Changing stroke color dynamically - c#

I am using stylus input to draw lines in a canvas. I want to change the color of the stroke with the pen pressure. so I used:
DrawingAttributes dattribute = new DrawingAttributes();
inkcan.EditingMode = InkCanvasEditingMode.Ink;
if (stylusInput.pressureFactor < 0.5)
dattribute.Color = Colors.Red;
else
dattribute.Color = Colors.Blue;
inkcan.DefaultDrawingAttributes = dattribute;
but I have found that the color changes only when I lift off and retouch the pen to tablet surface. I am not sure how to fix that problem.
Any help would be greatly appreciated.

Look at this question: InkCanvas Eraser
In the MSDN it states:
If you change the EraserShape, the cursor rendered on the InkCanvas is
not updated until the next EditingMode change.
The effect you are experiencing might be caused by the EditingMode being changed when you pull the pen off the canvas and put it back down.
If so, you could toggle the EditingMode property as I suggested in the linked answer.
EDIT
Have a look at this a 3rd down it says:
Off course, life is never as simple as that, so there is one more
little issue to handle. Apparently, the InkCanvas uses different
renderers for the final result compared to while the strokes are being
drawn. To show a transparency based on the pressure while the
drawing action is still in progress, we need to use the protected
property called DyamicRenderer which gets/sets the object used to
render the strokes on a drawing context while the stroke is being
drawn. This rendering object must be a descendent of DynamicRenderer.
All you need to do here is override the OnDraw method and change the
brush that is used. When you assign a new value to this property, the
InkCanvas actually changes an internal 'PlugIn list' which is called
whenever data is entered using the stylus.
This might be it.

The if condition is evaluated only once, so there is no reason for the colour to change while you are drawing. Unfortunately, there seems to be no "onpressurechanged" event, so you would probably have to set up a loop that checks the pressure every x milliseconds and adjusts the colour accordingly.
Since you don't want to freeze the UI, you would either need to run it in another thread and delegate the colour change back to the UI thread, or you can queue the pressure checks onto the window dispatcher with "applicationIdle" priority. This would look something like:
void checkPressure(InkCanvas inkcan)
{
//return if touch is lifted code here
DrawingAttributes dattribute = new DrawingAttributes();
if (stylusInput.pressureFactor < 0.5)
dattribute.Color = Colors.Red;
else
dattribute.Color = Colors.Blue;
inkcan.DefaultDrawingAttributes = dattribute;
this.Dispatcher.BeginInvoke(new MyPressureDelegate(checkPressure), DispatcherPriority.ApplicationIdle, inkcan);
}
assuming your stylusInput shares scope with the function, of course. Otherwise you'd need to pass it in along with the canvas in an object array.

Related

Controlling how, when, and if child controls are drawn (.NET)

I am writing an application in .NET that has a plugin interface. That plugin interface provides several ways to draw information (controls) onto the surface of the application window. While there are several reasons why I am doing this, the main reason is to provide custom colorization to text, either through the use of a graphic or directly manipulating the color of the text based on the background color. I do this through the use of a "text mask" which is a black and white bitmap that works as an "alpha" map to let the Paint method know where to apply the texture/color changes.
The plugin developer has the option of using regular text (such as with a label), mask text (which is drawn to the mask rather than as a regular control), OR letting the user decide. To go along with this, I have provided a modified label class that can either be drawn "normally' (when the text mask is not set for the control), or to the text mask when the User OR Developer decides (depending on what the plugin developer wishes to offer to the user). Here is the class's code so that you understand how this is being done:
public class MaskingLabel : Label
{
private static readonly SolidBrush maskBrush = new SolidBrush(Color.White);
public Bitmap Mask { get; set; }
public MaskingLabel() : base() { }
protected override void OnPaint(PaintEventArgs e)
{
if (Mask == null)
base.OnPaint(e);
else
{
Graphics g = Graphics.FromImage(Mask);
g.DrawString(Text, Font, maskBrush, Location);
}
}
}
The problem I am running into is that this approach requires that I handle controls in a very specific order so that the form is drawn correctly. I need to find the most efficient approach to get the tasks listed below done in the order given. I have thought of three possibilities discussed further down. For reference, this is the order in which tasks must be done:
All "MaskingLabel" controls that have the bitmap object set to the mask must be drawn first so that the mask is created before the next step.
The mask is applied to the background picture.
The resulting Bitmap is drawn in a way similar to the way a background would be drawn (except that it is modified first).
The rest of the controls are drawn as normal.
Is there a way for me to insure this happens without separating the controls manually? My first guess is no. As such, I have a few guesses below about how I should go about this. I was hoping someone with more in depth knowledge of GDI+ could offer some insight.
One idea that has occurred to me is to draw the masked controls during the OnPaintBackground method. However, I don't want to waste time by painting the controls twice. This means I would need to filter out which controls are drawn during the main Paint method which effectively leads us to option 2 (FAIK):
I can manually filter out the controls which draw to the mask so that they don't get added to the control. My question here though is would they get drawn at all? Can I manually force them to invoke the OnPaint method?
If doing that wouldn't work, then perhaps I can create a separate derived panel control to serve as a "backdrop" child control that acts as the background picture which can be forced to be drawn first?
EDIT (With Part of the answer):
I realized after posting this that I already have part of the solution built into my project. Still, I think it is a legitimate question to ask, so if anyone can add insight beyond what I have done in my description below, it is welcome.
Specifically, my project has only two controls that are added to the "root" form: a bar that goes to the top (docked at the top when it is shown), and a transparent panel that occupies the rest of the space (with a dock style set to fill). So my solution would be to add the mask controls to the main form and add all the rest to the panel. This only leaves one remaining issue to be resolved: How do I make sure that the panel and the bar are drawn last? (As part of step 4 in the first list?)

Background Panel Windows Form

I hope you can help me with this problem, attached videos to explain in a simpler way.
First example
Panel (has a textured background) with labels (the labels have a png image without background)
Events: MouseDown, MouseUp and MouseMove.
As you will notice in the video to drag the label the background turns white panel and regains its background image when I stop dragging the label
Panel controls have a transparent background as property, but changing the background with any color, let the problem occurred related to the substance, I do not understand why this happens and how to fix less.
Second Example
Contains the above, with the only difference that the panel controls instead of having transparent background, I chose black color for that property
You have to use double buffer and you don't have to stop using an image on the background, you can have everything running smoothly.
You have a couple of ways to do this, the fast way (not enough most of the time) is to enable doublebuffer of the panel.
The "slow" but better way is to do your own Double Buffer using a Bitmap object as a buffer.
This example creates a "side buffer" and accepts an image as parameter and draws it using created buffer.
public void DrawSomething(Graphics graphics, Bitmap yourimage)
{
Graphics g;
Bitmap buffer = new Bitmap(yourimage.Width, yourimage.Height, graphics);
g = Graphics.FromImage(buffer);
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.DrawImage(yourimage, 0, 0);
graphics.DrawImage(buffer, 0, 0);
g.Dispose();
}
Call this on your OnPaint event.
BTW... this is just a double buffer example.
Cheers
Change DoubleBuffered to true for both form and panel. I think that should solve your problem.
this is totally normal, because System.Windows.Forms.Control based items were not designed to do this kind of advanced Graphics operations.
in fact the reason that this effect happens here, is that when you assign any value other than 255 to the alpha component of a control BackColor, the form does the following when you change the control size or position:
it sets the new control position
it redraws the parent control
it gets the background of the control's parent as an image
it draws acquired image into the control body to seem as if the control is transparent
the control body gets drawn on top of the previously drawn background
the control children are drawn
* this is is a simplified explanation for the sake of illustration to deliver the idea
steps 1, 2 are responsible for the flickering effect that you see.
but you have two ways to solve this,
-the first is some kinda advanced solution but it's very powerful, which is you would have to create a double buffered custom control that would be your viewport.
the second is to use WPF instead of windows forms, as WPF was designed exactly to do this kind of things.
if you can kindly provide some code, i can show you how to do both.

Redraw panel contents after ClientSizeChanged

So my application runs in fixed size window and in full screen. The problem I'm facing is how to properly scale the current contents of the panel (which depend on the application use) when the window is resized. This is my current code:
private void Form1_ClientSizeChanged(object sender, EventArgs e)
{
System.Drawing.Drawing2D.Matrix transformMatrix = new System.Drawing.Drawing2D.Matrix();
float px = panel2.Width;
float py = panel2.Height;
panel2.Width = this.Width / 2;
panel2.Height = panel2.Width;
panel2.Location = new Point(this.Width - panel2.Width - 30, 30);
transformMatrix.Scale(panel2.Width / px, panel2.Height / py);
panel2.Region.Transform(transformMatrix);
//Rest of the code
}
But the drawn content doesn't scale, and if I use Invalidate() or Refresh() the drawn content gets cleared (the panel is redrawn empty). What am I missing?
.NET doesn't remember what's drawn on the panel, as simple as that. As soon as anything invalidates the windows bitmap buffer (causing a WM_PAINT), it's going to be repainted again. So, you have to draw what you want to draw using the Paint event (or overriding OnPaint).
However, there is another way that might be easier to implement - don't paint into a Panel. Instead, paint into a PictureBox (or rather, a Bitmap assigned to the Image property of the PictureBox). The Bitmap will be reused when invalidating (and redrawing) the picture box, so nothing will be lost. By using PictureBox.ScaleMode, you can define how you want the picture box to scale the bitmap, and it will do so as well as it can.
In any case, transforming the Region property doesn't do anything useful - you're simply changing the region, not doing anything to the drawing itself. To use 2D transformation matrices, you want to apply them on a Graphics object during the drawing (in Paint handler or OnPaint override) - drawing anything on the Graphics object will then transform everything you're trying to draw, which in your case means scaling the painting.
So you have to decide: do you want to just scale a stored bitmap with the painted image, or do you want to redraw it all from scratch (which also means you can pick any level of detail you can provide)?
I think that you're mistaking what the Region property is meant for. According to the MSDN docs (empasis mine, replace 'window' with 'control' when reading):
The window region is a collection of pixels within the window where the operating system permits drawing. The operating system does not display any portion of a window that lies outside of the window region. The coordinates of a control's region are relative to the upper-left corner of the control, not the client area of the control.
All that you're doing is changing the region that the OS will allow painting, which explains why you're not seeing anything. I think that you should be resizing the control when the form is resized, either through Anchor, or through my preference of Dock with several controls, or a panel like TableLayoutPanel where it will handle scaling and relative sizing for you.
Thank you for your answers, but I wrote my own function and logic that serves the purpose for this application. Basically the function checks for the state of the application variables, and calls the appropriate function that originally drew the content, and since those functions use the panel width and height as arguments they properly scale the drawn content and retain the drawing composition.
P.S. I'll accept Luaan's answers since it offers a valid alternative and is complete.

RenderTargets draw order in XNA

I think I am fundamentally misunderstanding the way render targets work. In my understanding RenderTargets are just Textures that the spritebatch draw calls draw to.
So I tried this code to render GUI windows in order to make sure they can only draw in their client area and its cropped outside that.
for (int i = Controls.Count - 1; i >= 0; i--)
{
RenderTarget2D oldTarget;
if (graphics.GetRenderTargets().Count() == 0) oldTarget = null;
else oldTarget = (RenderTarget2D)graphics.GetRenderTargets()[0].RenderTarget; // Get the old target being used.
graphics.SetRenderTarget(canvas); //set the target to a temporary RT
graphics.Clear(Color.Black); // Clear it
Control c = Controls[i]; // Get the current control (a form in this case)
c.Draw(spriteBatch, gameTime); // Draw it to the temp RT
graphics.SetRenderTarget(oldTarget); // Set the RT back to the main RT
Vector2 dest = c.DrawCoOrds(); // Gets the draw coordinates of the control
spriteBatch.Begin();
spriteBatch.Draw(canvas, new Rectangle((int)dest.X, (int)dest.Y, c.Bounds.Width, c.Bounds.Height), new Rectangle((int)dest.X, (int)dest.Y, c.Bounds.Width, c.Bounds.Height), Color.White);
// take the rect from the temp RT and draw it to the main RT.
spriteBatch.End();
}
However this code only draws the last form in the list which means it must be clearing the main RT somehow but i dont understand why. I only call clear when the RT is set to the temp canvas.
i think the best method to draw gui controls is with ScissorRectangle, because lets draw only inside that rectangle, that can be the client area of the gui control.
MSDN: GraphicsDevice.ScissorRectangle
You need to enable this funcionality through a RasterizerState.
RasterizerState ScissorState = new RasterizerState()
{
ScissorTestEnabled = true;
}
Before you draw, call SpriteBatch.Begin with this state.
A video of my own gui running in a xbox360 :)
How did you create your render target and back buffer? By default you can't write to a render target multiple times after changing to a different render target. This is why:
http://blogs.msdn.com/b/shawnhar/archive/2007/11/21/rendertarget-changes-in-xna-game-studio-2-0.aspx
You can change the default behavior by creating your render targets with RenderTargetUsage.PreserveContents., and the back buffer by overriding GraphicsDeviceManager.PrepareDeviceSettings., changing GraphicsDeviceInformation.PresentationParameters.RenderTargetUsage, as described in the link. Although I believe overriding those settings is done differently in XNA 4.
All that being said, changing away from the default behavior has performance considerations, and is not recommended. You should find a way to do this differently. One possibility would be to create a separate render target for each of your windows, draw all of them, switch to the back buffer and draw the render targets to it.
A better option would be to use the scissor rectangle rasterizer state as proposed by #Blau.

Gradient Drawing Bug

I'm having trouble with a gradient drawing call. I have a Form that looks like this.
Screenshot http://img413.imageshack.us/img413/3570/30364682.png
The problem is every now and again the above gradient drawing bug will start happening. It should go right across of course. Sometimes it only takes some build-rebuild-mashing to fix and it'll simply just "start" after a build every now and again.
That control (the top white part) is a TableLayoutPanel. The BackColor is set to white and on the panel's Paint event I do this:
/// <summary>
/// Draws the background gradient.
/// </summary>
private void titleBarLayoutPanel_Paint(object sender, PaintEventArgs e)
{
Brush brush = new LinearGradientBrush(titleBarLayoutPanel.Bounds, TaskHeaderLeftColor, TaskHeaderRightColor, LinearGradientMode.Horizontal);
e.Graphics.FillRectangle(brush, titleBarLayoutPanel.Bounds);
}
Should I be doing something else? The problem is that it works, and then without so much as a rebuild or build this will start happening!
EDIT I have since rebuilt the class library it is contained in (it's a generic Form) then rebuilt the app it's used in and the gradient is now filling across completely. This is bizarre.
Building and re-building your application, with no changes, normally doesn't solve this (or most any other bug for that matter) save the ones in which you run your application without doing a clean/rebuild first and then notice that the code you just wrote doesn't run (not sure that's possible these days with the IDEs). I see this a lot with newer devs when they keep rebuilding hoping that somehow the compiler will make the code "correct" or that maybe the compiler is simply not generating the correct code to begin with. (Please note that I do not mean the aforementioned statements to be taken disparagingly.)
To solve the issue at hand, you might try deriving your own TableLayoutPanel class in which you override the OnBackgroundPaint event, painting your own background, or simply returning if you don't want to paint your own background. (You seem to be painting the background in the Paint event). What you are doing in the code above is simply painting over the background already painted by the control, hence the "bug" you see, (double paint). It appears that the form is not resizable. Try making it resizable. Then resize it and watch it paint, or simply move other windows over it.
class CustomTableLayoutPanel : TableLayoutPanel
{
protected override void OnPaintBackground(PaintEventArgs e)
{
Brush brush = new LinearGradientBrush(this.ClientRectangle, TaskHeaderLeftColor, TaskHeaderRightColor, LinearGradientMode.Horizontal);
e.Graphics.FillRectangle(brush, this.ClientRectangle);
//base.OnPaintBackground(e);
}
}
By the way, you should replace Bounds with ClientRectangle.
Bounds is the control's rectangle relative to its parent; ClientRectangle is relative to the control itself.
In this particular case, it won't make a difference, since the control is at 0, 0.

Categories