Is it possible to turn off anti-aliasing in WPF when using an ImageBrush?
Given the following code:
var handleImage = new BitmapImage(new Uri($"pack://application:,,,/Resources/myimage.png"));
var imageBrush = new ImageBrush(handleImage);
imageBrush.AlignmentY = AlignmentY.Top;
imageBrush.AlignmentX = AlignmentX.Left;
imageBrush.Stretch = Stretch.Uniform;
imageBrush.Viewport = new Rect(0, 0, _handleImage.Width, _handleImage.Height);
imageBrush.ViewportUnits = BrushMappingMode.Absolute;
imageBrush.TileMode = TileMode.Tile;
drawingContext.DrawRectangle(imageBrush, null, new Rect(0, 0, width, height));
Gives me something like:
But I'm expecting:
WPF's default antialiasing makes it look terrible. I've tried UseLayoutRounding=true, SnapsToDevicePixels=true, RenderOptions.SetBitmapScalingMode(this, BitmapScalingMode.HighQuality),
RenderOptions.SetBitmapScalingMode(this, BitmapScalingMode.NearestNeighbor) RenderOptions.SetEdgeMode(this, EdgeMode.Unspecified); on the window. The only one that changes any visual difference is BitmapScalingMode.NearestNeighbor however it still looks odd and the tiling overlaps itself.
EDIT: Download full working sample: WpfImageBrushExample.zip
The problem here was that the BitmapImage width and height were different fractional numbers - in this case it was 5.333 x 6x666 instead of the expected 4 x 5 pixels. If I use _handleImage.PixelWidth and _handleImage.PixelHeight the problem is fixed and I don't get weird aliasing under any of those rendering options set.
var handleImage = new BitmapImage(new Uri($"pack://application:,,,/Resources/myimage.png"));
var imageBrush = new ImageBrush(handleImage);
imageBrush.AlignmentY = AlignmentY.Top;
imageBrush.AlignmentX = AlignmentX.Left;
imageBrush.Stretch = Stretch.Uniform;
imageBrush.Viewport = new Rect(0, 0, _handleImage.PixelWidth, _handleImage.PixelHeight);
imageBrush.ViewportUnits = BrushMappingMode.Absolute;
imageBrush.TileMode = TileMode.Tile;
drawingContext.DrawRectangle(imageBrush, null, new Rect(0, 0, width, height));
Produces:
Related
I'm trying to create a simple sketching application.
But I've ran into a weird problem. I have a Surface Pro 3 that I'm working on, with a DPI of 144, according to some system settings.
When I save the image from my app, using 96 as the dpi, it produces an image that's just a little bit smaller. Which is kind of weird.
Is there a way that I can either a) scale the canvas/strokes up that I'm saving, or tell the RenderTargetBitmap to scale properly? If I just stick 144 in there, I get the proper scale for the strokes, but my canvas size is borked.
My canvas saving code looks like this:
public void saveCanvas(object sender, RoutedEventArgs e){
this.save_filename = this.save_filename ?? this.getSaveFilename();
if (save_filename != null){
var cantwo = new InkCanvas();
cantwo.Strokes.Clear();
cantwo.Strokes.Add(this.canvas.Strokes);
cantwo.Background = this.canvas.Background;
var size = new Size(this.canvas.ActualWidth, this.canvas.ActualHeight);
cantwo.Height = size.Height;
cantwo.Width = size.Width;
cantwo.Measure(size);
cantwo.Arrange(new Rect(size));
var transform = this.canvas.LayoutTransform;
var rtb = new RenderTargetBitmap((int)this.canvas.ActualWidth, (int)this.canvas.ActualHeight, dpiX, dpiY, PixelFormats.Default);
rtb.Render(cantwo);
try {
using(var fs = File.Open(this.save_filename, FileMode.Create)){
var encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
encoder.Save(fs);
}
}
catch(IOException){
MessageBox.Show("Failed to save image", "ERROR: Save Failed");
}
// Restore transformation if there was one.
this.canvas.LayoutTransform = transform;
}
}
How can I save my image at the same size/resolution/dpi as my canvas? (Or draw on my canvas at the same dpi/scale as I save my image)?
Instead of creating a second InkCanvas, draw a Rectangle that is filled with a VisualBrush into a DrawingVisual:
var rect = new Rect(canvas.RenderSize);
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
dc.DrawRectangle(new VisualBrush(canvas), null, rect);
}
var rtb = new RenderTargetBitmap(
(int)rect.Width, (int)rect.Height, 96, 96, PixelFormats.Default);
rtb.Render(visual);
I'm trying to print an image in the center of the page but I can't come up with any idea.
System.Windows.Point printLocation = new System.Windows.Point(50,50);
printLocation.X = pageWidth - 50 / 2; 50 is the margin
imageViewer = ImagePrintAdapter.CreateImageFromBitmapImage(img,printLocation);
printerDialog.PrintVisual(imageViewer, "Identification");
This is the CreateImageFromBitmapImagemethod
public static System.Windows.Controls.Image CreateImageFromBitmapImage(BitmapImage imgSource, System.Windows.Point imgLocation)
{
System.Windows.Controls.Image imageViewer = new System.Windows.Controls.Image();
imageViewer.BeginInit();
imageViewer.Source = imgSource;
imageViewer.Measure(new System.Windows.Size(double.PositiveInfinity, double.PositiveInfinity));
imageViewer.Arrange(new System.Windows.Rect(imgLocation, imageViewer.DesiredSize));
imageViewer.EndInit();
imageViewer.UpdateLayout();
return imageViewer;
}
If I set the printLocation.X to be the half of the pageWidth, shouldn't it start at the center ?
You may simply draw the image into a DrawingVisual and print it. The following simplified example assumes that the bitmap size is smaller than the printable area size:
ImageSource image = ...
var rect = new Rect(
(printDialog.PrintableAreaWidth - image.Width) / 2,
(printDialog.PrintableAreaHeight - image.Height) / 2,
image.Width, image.Height);
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
dc.DrawImage(bitmap, rect);
}
printDialog.PrintVisual(visual, "");
Note that you may as well use any other size for the Rectangle, i.e. scale the printed image accordingly.
So I am producing a transparent PNG using a DrawingContext and DrawingVisual.
Inside the DrawingContext, I drew a rectange.
I would now like to "cut out" a circle inside of the rectangle. How do I do this? I did not find any functions in drawing context to clear a region.
You can try using CombinedGeometry to combine 2 geometries (each time). It has GeometryCombineMode allowing you to specify some logic combination. In this case what you need is GeometryCombineMode.Xor. The intersection of the Rect and the Ellipse (cirlce) will be cut out. Here is the simple code demonstrating it:
DrawingVisual dv = new DrawingVisual();
using (var dc = dv.RenderOpen()) {
var rect = new Rect(0, 0, 300, 200);
var cb = new CombinedGeometry(GeometryCombineMode.Xor,
new RectangleGeometry(rect),
new EllipseGeometry(new Point(150, 100), 50, 50));
dc.DrawGeometry(Brushes.Blue, null, cb);
}
I hope you know how to render the DrawingVisual. You can use some RenderTargetBitmap to capture it into some kind of BitmapSource and then you have many ways to show this bitmap.
Here is the screenshot:
The Black region means the color is transparent.
In case you want to cut out some complex image (such as drawn text or image). You can turn the CombinedGeometry into some kind of OpacityMask (type of Brush). We can turn it into a DrawingBrush and this brush can be used as OpacityMask which can be passed into DrawingContext.PushOpacityMask method:
DrawingVisual dv = new DrawingVisual();
using (var dc = dv.RenderOpen()) {
var rect = new Rect(0, 0, 300, 200);
var cb = new CombinedGeometry(GeometryCombineMode.Xor,
new RectangleGeometry(rect),
new EllipseGeometry(new Point(150, 100), 50, 50));
var mask = new DrawingBrush(new GeometryDrawing(Brushes.Blue, null, cb));
dc.PushOpacityMask(mask);
dc.DrawImage(someImage, rect);
dc.DrawText(new FormattedText("Windows Presentation Foundation",
System.Globalization.CultureInfo.CurrentCulture,
System.Windows.FlowDirection.LeftToRight,
new Typeface("Lucida Bright"), 30, Brushes.Red){
MaxTextWidth = rect.Width,
MaxTextHeight = rect.Height,
TextAlignment = TextAlignment.Center
}, new Point());
}
Note that the rect should have the size of your whole drawing. Then positioning the hole and other drawn stuff will be exact as what you want.
Finally the DrawingVisual also has a useful property called Clip which is a Geometry. So you can prepare some CombinedGeometry and assign it to DrawingVisual.Clip property.
Suppose you already have your DrawingVisual (with some drawn stuff including text, images, ...). The following code will punch a hole through it:
//prepare the geometry, which can be considered as the puncher.
var rect = new Rect(0, 0, 300, 200);
var cb = new CombinedGeometry(GeometryCombineMode.Xor,
new RectangleGeometry(rect),
new EllipseGeometry(new Point(150, 100), 50, 50));
//punch the DrawingVisual
yourDrawingVisual.Clip = cb;
Actually, I have to print the view from a Viewport3D, obviously, I use a RenderTargetBitmap. The problem is that if the resolution of the rendered image got high, some triangles of my scene don't appear on my final image.
For example, my viewport can be 1024*768 and the resolution I use with my RenderTargetBitmap would be 3 times viewport's resolution.
http://imgur.com/PS2F9D9
I already solved the problem in a certain way... In fact, triangles don't appear when I use a big scale. If I lower the size of my RenderTargetBitmap, it will contain everything.
Actually, I have more or less 1024*768 at 96dpi. If I want to impress at 300dpi, I need to get a huge image so I would like to avoid this last solution.
Some code :
public static RenderTargetBitmap CaptureEcran(Viewport3D p_viewPort, int p_scale)
{
RenderTargetBitmap l_bmp;
p_scale = p_scale > 5 ? 5 : p_scale;
l_bmp = new RenderTargetBitmap(p_scale * Convert.ToInt32(p_viewPort.ActualWidth), p_scale * Convert.ToInt32(p_viewPort.ActualHeight), p_scale * 96.0, p_scale * 96.0, PixelFormats.Pbgra32);
DrawingVisual vis = new DrawingVisual();
DrawingContext dc = vis.RenderOpen();
dc.DrawRectangle(System.Windows.Media.Brushes.White, null, new Rect(0, 0, p_scale * p_viewPort.ActualWidth, p_scale * p_viewPort.ActualHeight));
dc.Close();
l_bmp.Render(vis);
p_viewPort.UpdateLayout();
l_bmp.Render(p_viewPort);
return l_bmp;
}
public static void SaveImage(RenderTargetBitmap renderTargetBitmap, string m_impression)
{
System.Windows.Forms.FolderBrowserDialog l_fBD = new System.Windows.Forms.FolderBrowserDialog();
l_fBD.ShowDialog();
string l_path = l_fBD.SelectedPath + "\\" + CurrentUser() + "__" + CurrentDate() + "__." + m_impression.ToLower();
FileStream stream = new FileStream(l_path, FileMode.Create);
BitmapEncoder l_encoder = null;
switch(m_impression){
case "PNG":
PngBitmapEncoder l_png = new PngBitmapEncoder();
l_encoder = l_png;
break;
case "JPEG":
JpegBitmapEncoder l_jpeg = new JpegBitmapEncoder();
l_jpeg.QualityLevel = 30;
l_encoder = l_jpeg;
break;
}
l_encoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
l_encoder.Save(stream);
stream.Close();
}
My call is :
SaveImage(CaptureEcran(m_viewPortCourant.ViewPort3D,5), m_impression);
Where m_impression is .png or .jpg
It finally works by using a VisualBrush containing the viewport and drawn into the DrawingContext.
DrawingVisual vis = new DrawingVisual();
DrawingContext dc = vis.RenderOpen();
VisualBrush sourceBrush = new VisualBrush(p_viewPort);
dc.DrawRectangle(System.Windows.Media.Brushes.White, null, new Rect(0, 0, p_viewPort.ActualWidth * p_scale, p_viewPort.ActualHeight * p_scale));
dc.DrawRectangle(sourceBrush, null, new Rect(new System.Windows.Point(0, 0), new Vector(p_viewPort.ActualWidth, p_viewPort.ActualHeight)));
dc.Close();
l_bmp.Render(vis);
I have a requirement of moving dynamically created rectangles dynamically.
I am almost done with my implementation, and if I write the code below for each of the rectangle dynamically, I am able to achieve the desired result.
The code below moves the desired rectangle by 50 pixels on X Axis.
TranslateTransform translateTransform1 = new TranslateTransform(50, 0); aRectangle.RenderTransform = translateTransform1;
The problem is I want this to be animated. The code below is written to have the same rectanle moved with animation, but gives entirelly different result all together. Any help will be much appreciated. I want it to manage through code as my rectangles are going to be dynamic.
TranslateTransform translateTransform1 = new TranslateTransform(50, 0);
Duration duration = new Duration(new TimeSpan(0, 0, 0, 1, 0));
DoubleAnimation anim = new DoubleAnimation(30, duration);
translateTransform1.BeginAnimation(TranslateTransform.XProperty, anim);
aRectangle.RenderTransform = translateTransform1;
Try
Duration duration = new Duration(new TimeSpan(0, 0, 0, 1, 0));
DoubleAnimation anim = new DoubleAnimation(30, duration);
aRectangle.RenderTransform = new TranslateTransform();
aRectangle.BeginAnimation(TranslateTransform.XProperty, anim);