InkCanvas not committing strokes before PreviewMouseUp when using touch - c#

I'm using a InkCanvas with a record as you draw feature.
As you can see in this gif, created by drawing using a mouse:
When the user fires a PreviewMouseDown event, I simply start the capture, frame by frame base on a time.
The capture is done by a simple render:
public static RenderTargetBitmap GetRender(this UIElement source, double dpi)
{
var bounds = VisualTreeHelper.GetDescendantBounds(source);
var scale = Math.Round(dpi / 96d, 2);
var width = (bounds.Width + bounds.X) * scale;
var height = (bounds.Height + bounds.Y) * scale;
#region If no bounds
if (bounds.IsEmpty)
{
var control = source as Control;
if (control != null)
{
width = control.ActualWidth * scale;
height = control.ActualHeight * scale;
}
bounds = new Rect(new System.Windows.Point(0d, 0d), new System.Windows.Point(width, height));
}
#endregion
var rtb = new RenderTargetBitmap((int)Math.Round(width), (int)Math.Round(height), dpi, dpi, PixelFormats.Pbgra32);
var dv = new DrawingVisual();
using (var ctx = dv.RenderOpen())
{
var vb = new VisualBrush(source);
var locationRect = new System.Windows.Point(bounds.X, bounds.Y);
var sizeRect = new System.Windows.Size((int)Math.Round(bounds.Width), (int)Math.Round(bounds.Height));
ctx.DrawRectangle(vb, null, new Rect(locationRect, sizeRect));
}
rtb.Render(dv);
return (RenderTargetBitmap)rtb.GetAsFrozen();
}
Now, the problem is that when using touch, for some reason, the strokes are not available when the render occurs. But they are being displayed normally for me:
As you can see, the recorder still captures all necessary frames, but the strokes are only "there" when the PreviewMouseUp event occurs.
What can I do to fix this issue?

Related

Rotate Image in Win2D

I'm trying to rotate image but couldn't get expected result.
I've tried with WIn2D but couldn't make image as expected.
My tried Code
public async void Rotate(string originalImagepath, Rect originalImageRect, float degrees)
{
int height = (int)Math.Sqrt(originalImageRect.Width * originalImageRect.Width + originalImageRect.Height * originalImageRect.Height);
CanvasDevice device = CanvasDevice.GetSharedDevice();
CanvasRenderTarget webCardImage = null;
CanvasBitmap bitmap = null;
var logicalDpi = DisplayInformation.GetForCurrentView().LogicalDpi;
Vector2 endpoint = new Vector2((float)originalImageRect.Width / 2, (float)originalImageRect.Height / 2);
try
{
webCardImage = new CanvasRenderTarget(device, height, height, logicalDpi);
using (var ds = webCardImage.CreateDrawingSession())
{
ds.Clear(Colors.Transparent);
using (FileStream imageStream = new FileStream(originalImagepath, FileMode.Open))
{
IRandomAccessStream fileStream = imageStream.AsRandomAccessStream();
bitmap = await CanvasBitmap.LoadAsync(device, fileStream);
}
ICanvasImage image = new Transform2DEffect
{
Source = bitmap,
TransformMatrix = Matrix3x2.CreateRotation(degrees, endpoint),
};
var sourceRect = image.GetBounds(ds);
ds.DrawImage(image, new Rect(originalImageRect.X, originalImageRect.Y, originalImageRect.Width, originalImageRect.Height), sourceRect, 1, CanvasImageInterpolation.HighQualityCubic);
}
}
catch (Exception ex)
{
}
//Save Image Code here
}
Expected Output:
My generated Image:
NB: Rect originalImageRect refers main image rect which i want to rotate.
When this happens, it means that after your image is rotated, the size of the target rendering rectangle (may be height or width) is smaller than the size of the rotated image, resulting in compression of the image.
I analyzed your code, you tried to create a square with the originalImageRect diagonal length as the side length, to ensure that the image will not exceed the rendering range when rotating around the center.
This is good, but there are some deviations when rendering.
The image is in the upper left corner of CanvasRenderTarget by default. If no displacement is performed, the center point is on the upper left when rotating, which means it will still exceed the rendering range.
You can try this:
double height = Math.Sqrt(originalImageRect.Width * originalImageRect.Width + originalImageRect.Height * originalImageRect.Height);
float y = (float)((height - originalImageRect.Height) / 2.0f);
float x = (float)((height - originalImageRect.Width) / 2.0f);
Vector2 endpoint = new Vector2((float)height / 2, (float)height / 2);
try
{
webCardImage = new CanvasRenderTarget(MyCanvas, (float)height, (float)height, logicalDpi);
using (var ds = webCardImage.CreateDrawingSession())
{
// init CanvasBitmap
ICanvasImage image = new Transform2DEffect
{
Source = bitmap,
TransformMatrix = Matrix3x2.CreateTranslation(new Vector2(x,y))
* Matrix3x2.CreateRotation(degrees,endpoint)
};
var sourceRect = image.GetBounds(ds);
ds.DrawImage(image);
}
}
catch (Exception ex)
{
}
We first shift the image, move it to the center of the canvas, and then rotate it around the center of the canvas, so that we can achieve our goal.
In addition, when using the DrawImage() method, there is no need to additionally define Rect for rendering constraints.
Thanks.

Rasterize wpf textblock into a bitmap via drawingcontext

My program is sort of copy version of MS paint and Pickpick.
and one of features is rasterizing the selected object such as textblock or shape.
Regarding the selectable object, in order to resize and move with adorner,
it has 1 ContentControl which comprise 1 textblock + 1 shape.
ContentControl (able to resize, rotate, move)
└─> Textblock (bold, italic, V-align, H-align, word wrap...)
└─> Shape (can be a triangle, rectangle etc...)
It was not hard to convert to draw the shape with drawing context instead of render at Canvas.
var SH = CC.GetShape();
var TB = CC.GetTextBlock();
var visual = new DrawingVisual();
Geometry geo = null;
System.Windows.Media.Pen pen = null;
System.Windows.Media.Brush brush = null;
if (SH != null)
{
geo = SH.RenderedGeometry; // shape to geo
if (geo == null)
return;
pen = new System.Windows.Media.Pen(SH.Stroke, SH.StrokeThickness);
brush = SH.Fill;
}
using (var dc = visual.RenderOpen())
{
// Draw the background first
dc.DrawImage(first, new Rect(0, 0, first.Width, first.Height));
dc.PushTransform(new TranslateTransform(left, top));
// Draw the shape
if (SH != null && geo != null)
dc.DrawGeometry(brush, pen, geo);
}
But while drawing Textblock with drawing context,
I've referred below link to calculate the position of Textblock
Vertical alignment with DrawingContext.DrawText
but the problem is when the Textblock has multiline or word wrapped.
screenshot of my program
if (TB.Text.Equals(string.Empty) == false)
{
var typeface = new Typeface(CC.txtSetting.fontFamily,
CC.txtSetting.fontStyle,
CC.txtSetting.fontWeight,
FontStretches.Normal);
var formattedText = new FormattedText(TB.Text
, CultureInfo.CurrentCulture
, FlowDirection.LeftToRight
, typeface
, CC.txtSetting.fontSize
, new SolidColorBrush(CC.txtSetting.fontColor));
double centerX = CC.ActualWidth / 2;
double centerY = CC.ActualHeight / 2;
double txtPositionX = 0.0f;
double txtPositionY = 0.0f;
if (TB.TextAlignment == TextAlignment.Left)
{
txtPositionX = 1.0f;
}
else if (TB.TextAlignment == TextAlignment.Center)
{
txtPositionX = centerX - formattedText.WidthIncludingTrailingWhitespace / 2;
}
else if (TB.TextAlignment == TextAlignment.Right)
{
txtPositionX = CC.Width -
formattedText.WidthIncludingTrailingWhitespace - 1.0f;
}
if (TB.VerticalAlignment == VerticalAlignment.Top)
{
txtPositionY = 1.0f;
}
else if (TB.VerticalAlignment == VerticalAlignment.Center)
{
txtPositionY = centerY - formattedText.Height / 2;
}
else if (TB.VerticalAlignment == VerticalAlignment.Bottom)
{
txtPositionY = CC.Height - formattedText.Height - 1.0f;
}
var ptLocation = new System.Windows.Point(txtPositionX, txtPositionY);
dc.DrawText(formattedText, ptLocation);
}
Additionally, the textblock is wrapped by ContentControl so depending on user change the property of textblock, it will vary so much.
I guess it seems not possible to convert every variable.
So, I'm thinking alternative ways to draw.
Draw with GDI+ instead of drawing with drawing context. (still uncertain)
Use drawing context while the user is editing the text. (so it'll be the same before rasterizing and vice-versa)
Any way to directly convert/capture the Textblock into an image or Geometry? (it would be the best way if it's possible.)
For example, to get a shader effect applied image source, I did like this. so.. probably there's the way.
How can I get the object of effect-applied source
You can also refer to this program from http://ngwin.com/picpick
screenshot of picpick
Any better ideas? Thank you in advance.
I made it!
I could capture the particular control with RenderTargetBimap. Since ContentControl is a part of Visual Element.
CustomControl is inherited control from ContentControl.
public static BitmapSource ControlToBitmap(CustomControl control)
{
int W = (int)control.ActualWidth;
int H = (int)control.ActualHeight;
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(
W, H,
96d, 96d, PixelFormats.Pbgra32);
// needed otherwise the image output is black
control.Measure(new System.Windows.Size(W, H));
control.Arrange(new Rect(new System.Windows.Size(W, H)));
renderBitmap.Render(control);
var BS = RenderTargetBitmapToBitmap(renderBitmap);
return BS;
}
Additionally, I had to deal with the angle. Because I couldn't capture the angled control directly. but my idea is
back up the angle value first.
And restore the control to be non-rotated(RotateTransform = 0.0)
Capture a non-rotated control to a bitmap.
Then rotate the captured bitmap again.
Combine both bitmaps into one.
public static void OverlayControl(ImageSource first, CustomControl CC)
{
if (CC == null)
return;
var visual = new DrawingVisual();
double left = Canvas.GetLeft(CC);
double top = Canvas.GetTop(CC);
// Get control's angle.
double rotationInDegrees = 0.0f;
RotateTransform rotation = CC.RenderTransform as RotateTransform;
if (rotation != null) // Make sure the transform is actually a RotateTransform
{
rotationInDegrees = rotation.Angle; // back up this to temp var.
rotation.Angle = 0.0f; // Set this to 0.0 to capture properly.
}
var second = ControlToBitmap(CC);
using (var dc = visual.RenderOpen())
{
// Draw the background image frist.
dc.DrawImage(first, new Rect(0, 0, first.Width, first.Height));
// Push angle if the control has rotated.
if (rotationInDegrees != 0.0f)
dc.PushTransform(new RotateTransform(rotationInDegrees, left + (CC.Width / 2), top + (CC.Height / 2)));
// transfrom as much as control moved from the origin.
dc.PushTransform(new TranslateTransform(left, top));
// Draw the second image. (captured image from the control)
dc.DrawImage(second, new Rect(0, 0, second.Width, second.Height));
// pop transforms
dc.Pop();
}
var rtb = new RenderTargetBitmap((int)first.Width, (int)first.Height,
96, 96, PixelFormats.Default);
rtb.Render(visual);
// Set as a one combined image.
MainWindow.VM.RenderedImage = rtb;
}
Now, everything seems alright.

Why is my Shapes.Line twice the size I want it to be on Canvas?

Using this method, I want to render a canvas to a bitmap.
When I add a Shape to the Canvas, it is rendered twice the specified size.
In the example below, I am drawing a line from (0;0) to (50;50) on a canvas of size 200 by 200.
public bool exportToBmp(string path, int dpi = 96)
{
if (path == null)
return false;
var canvas = new System.Windows.Controls.Canvas();
// This diagonal Line should span a quarter of the rendered Image
var myLine = new System.Windows.Shapes.Line();
myLine.Stroke = System.Windows.Media.Brushes.LightSteelBlue;
myLine.X1 = 0;
myLine.X2 = 50;
myLine.Y1 = 0;
myLine.Y2 = 50;
myLine.StrokeThickness = 2;
canvas.Children.Add(myLine);
canvas.Height = 200;
canvas.Width = 200;
Size size = new Size(canvas.Width, canvas.Height);
canvas.Measure(size);
canvas.Arrange(new Rect(size));
var width = (int)canvas.ActualWidth;
var height = (int)canvas.ActualHeight;
RenderTargetBitmap bmp = new RenderTargetBitmap(width, height, dpi, dpi, PixelFormats.Pbgra32);
bmp.Render(canvas);
PngBitmapEncoder image = new PngBitmapEncoder();
image.Frames.Add(BitmapFrame.Create(bmp));
using (Stream fs = File.Create(path))
{
image.Save(fs);
}
return false;
}
The rendered image I get is 200 by 200 px big, but the diagonal goes all the way to (100;100)
What am I doing wrong?
When I run your code, I see the following image (border added for clarity):
Are you passing a DPI other than 96?
What DPI settings are in use on your computer?

How to save a non-fuzzy image of a control in WPF?

I am using a DrawingContext to draw images. I then render the result to a RenderTargetBitmap. I also render a Canvas to the same RenderTargetBitmap. Even though the pixel boundaries are crisp on screen, they become blurred and fuzzy when saved.
In the screenshot below, you can see the issue (with BitmapScalingMode = NearestNeighbor).
Here it is with BitmapScalingMode = HighQuality. It's smoother but not crisp and clean.
Here is the relevant section of my code. You can see that I tried setting the RenderOptions at multiple places but it seems to have no effect.
DrawingVisual drawingVisual = new DrawingVisual();
RenderTargetBitmap result = new RenderTargetBitmap((int)size.Width, (int)size.Height, 96, 96, PixelFormats.Pbgra32);
RenderOptions.SetBitmapScalingMode(drawingVisual, BitmapScalingMode.NearestNeighbor); // This forces the scaling to be on even-pixel boundaries
RenderOptions.SetBitmapScalingMode(drawCanvas, BitmapScalingMode.NearestNeighbor); // This forces the scaling to be on even-pixel boundaries
RenderOptions.SetBitmapScalingMode(result, BitmapScalingMode.NearestNeighbor); // This forces the scaling to be on even-pixel boundaries
using (DrawingContext context = drawingVisual.RenderOpen()) {
context.DrawRectangle(Brushes.Black, null, new Rect(new Point(), new Size(size.Width, size.Height)));
if (layers.Count >= 1 && layers[0].LayerImage != null && layers[0].LayerImage.Source != null && gridImage.Children[1].Visibility == System.Windows.Visibility.Visible)
context.DrawImage(layers[0].LayerImage.Source, new Rect(size)); // Draw first image.
context.Close();
}
result.Render(drawingVisual);
drawCanvas.Measure(drawCanvas.RenderSize);
drawCanvas.Arrange(new Rect(drawCanvas.RenderSize));
for (int i = 0; i < drawCanvas.Children.Count; i++) {
RenderOptions.SetBitmapScalingMode(drawCanvas.Children[i], BitmapScalingMode.NearestNeighbor); // This forces the scaling to be on even-pixel boundaries
}
result.Render(drawCanvas);
BitmapEncoder encoder = new PngBitmapEncoder();
if (result!= null) {
encoder.Frames.Add(BitmapFrame.Create((BitmapSource)result));
encoder.Save(fileStream);
}
I don't know if you have fixed that but that function works very good on my side.
public BitmapSource SnapShotPNG(UIElement source)
{
double actualWidth = source.RenderSize.Width;
double actualHeight = source.RenderSize.Height;
RenderTargetBitmap renderTarget = new RenderTargetBitmap((int)actualWidth, (int)actualHeight, 96, 96, PixelFormats.Pbgra32);
DrawingVisual visual = new DrawingVisual();
using (DrawingContext context = visual.RenderOpen())
{
VisualBrush sourceBrush = new VisualBrush(source);
context.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(actualWidth, actualHeight)));
}
source.Measure(source.RenderSize); //Important
source.Arrange(new Rect(source.RenderSize)); //Important
renderTarget.Render(visual);
try
{
return new CroppedBitmap(renderTarget, new Int32Rect(0, 0, (int)actualWidth, (int)actualHeight));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return null;
}
}
public static BitmapSource CaptureScreen(this UIElement visualElement, int? desiredLongestEdge = null)
{
double scale = 1;
if (desiredLongestEdge.HasValue)
{
if (visualElement.RenderSize.Width > visualElement.RenderSize.Height)
{
scale = desiredLongestEdge.Value/ visualElement.RenderSize.Width;
}
else
{
scale = desiredLongestEdge.Value / visualElement.RenderSize.Height ;
}
}
var targetBitmap =
new RenderTargetBitmap(
(int) Math.Ceiling(scale * (visualElement.RenderSize.Width + 1)),
(int) Math.Ceiling(scale * (visualElement.RenderSize.Height + 1)),
scale * 96,
scale * 96,
PixelFormats.Pbgra32);
visualElement.Measure(visualElement.RenderSize); //Important
visualElement.Arrange(new Rect(visualElement.RenderSize)); //Important
targetBitmap.Render(visualElement);
return targetBitmap;
}

Saving an image of a UIElement rendered larger than it appears on screen

I have a chart that I'm displaying to the user and I want to be able to export the chart as an image to disk so they can use it outside of the application (for a presentation or something).
I've managed to get the basic idea working using PngBitmapEncoder and RenderTargetBitmap but the image I get out of it is way to small to really be usable and I want to get a much larger image.
I tried to simply increase the Height and Width of the control I was wanting to render, but the parent seems to have direct control on the render size. From this I tried to duplicate the UIElement in memory but then the render size was (0,0) and when I tried to use methods to get it to render, such as Measure() Arrange() and UpdateLayout() they throw exceptions about needing to decouple the parent to call these, but as it's in memory and not rendered there shouldn't be a parent?
This is all done with Visiblox charting API
Here is what I've got currently, except it doesn't work :(
var width = 1600;
var height = 1200;
var newChart = new Chart { Width = width, Height = height, Title = chart.Title, XAxis = chart.XAxis, YAxis = chart.YAxis, Series = chart.Series};
Debug.WriteLine(newChart.RenderSize);
var size = new Size(width, height);
newChart.Measure(size);
newChart.Arrange(new Rect(size));
newChart.UpdateLayout();
Debug.WriteLine(newChart.RenderSize);
var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
rtb.Render(newChart);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
using (var stream = fileDialog.OpenFile())
encoder.Save(stream);
I've gotten a bit closer, it now render the graph background the axis' etc. but just not the actual lines that are being graphed. Below is an updated source
public static void RenderChartToImage(Chart elementToRender, string filename)
{
if (elementToRender == null)
return;
Debug.Write(elementToRender.RenderSize);
var clone = new Chart();
clone.Width = clone.Height = double.NaN;
clone.HorizontalAlignment = HorizontalAlignment.Stretch;
clone.VerticalAlignment = VerticalAlignment.Stretch;
clone.Margin = new Thickness();
clone.Title = elementToRender.Title;
clone.XAxis = new DateTimeAxis();
clone.YAxis = new LinearAxis() { Range = Range<double>)elementToRender.YAxis.Range};
foreach (var series in elementToRender.Series)
{
var lineSeries = new LineSeries
{
LineStroke = (series as LineSeries).LineStroke,
DataSeries = series.DataSeries
};
clone.Series.Add(lineSeries);
}
var size = new Size(1600, 1200);
clone.Measure(size);
clone.Arrange(new Rect(size));
clone.UpdateLayout();
Debug.Write(clone.RenderSize);
var height = (int)clone.ActualHeight;
var width = (int)clone.ActualWidth;
var renderer = new RenderTargetBitmap(width, height, 96d, 96d, PixelFormats.Pbgra32);
renderer.Render(clone);
var pngEncoder = new PngBitmapEncoder();
pngEncoder.Frames.Add(BitmapFrame.Create(renderer));
using (var file = File.Create(filename))
{
pngEncoder.Save(file);
}
}
So I get out something like this:
Which while big, isn't useful as it has nothing charted.
http://www.visiblox.com/blog/2011/05/printing-visiblox-charts
The main point I was missing was
InvalidationHandler.ForceImmediateInvalidate = true;
Setting this before I rendered the chart in memory and then reverting it once I had finished. From there it was smooth sailing :D
RenderTargetBitmap DrawToImage<T>(T source, double scale) where T:FrameworkElement
{
var clone = Clone(source);
clone.Width = clone.Height = Double.NaN;
clone.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
clone.VerticalAlignment = System.Windows.VerticalAlignment.Stretch;
clone.Margin = new Thickness();
var size = new Size(source.ActualWidth * scale, source.ActualHeight * scale);
clone.Measure(size);
clone.Arrange(new Rect(size));
var renderBitmap = new RenderTargetBitmap((int)clone.ActualWidth, (int)clone.ActualHeight, 96, 96, PixelFormats.Pbgra32);
renderBitmap.Render(clone);
return renderBitmap;
}
static T Clone<T>(T source) where T:UIElement
{
if (source == null)
return null;
string xaml = XamlWriter.Save(source);
var reader = new StringReader(xaml);
var xmlReader = XmlTextReader.Create(reader, new XmlReaderSettings());
return (T)XamlReader.Load(xmlReader);
}
I think these might help :
Exporting Visifire Silverlight Chart as Image with a downloadable silverlight solution.
Exporting Chart as Image in WPF

Categories