I have an application relying on Deep zoom images (convertion from a PNG to a pyramid of JPGs in various scale) which we use DeepZoomTools.dll for. This is relying on PresentationCore.dll and has been working fine for years.
After the rollout of KB4040972 and KB4040973, the conversion from PNG to JPG generates (depending on coordinates) black images instead of the image it should contain.
If the below code is run in a console or desktop app, it works.
It ONLY doesn't work if run under high privilege SYSTEM account (e.g. from Task scheduler).
I have created a project to reproduce the issue, code below:
public static void TestConvert2(string strFileName, string strOutFileName) {
JpegBitmapEncoder jpegBitmapEncoder = new JpegBitmapEncoder();
jpegBitmapEncoder.QualityLevel = 1 + (int) Math.Round(0.95 * 99.0);
BitmapEncoder encoder = jpegBitmapEncoder;
Int32Rect inputRect = new Int32Rect(0, 0, 255, 255);
Rect outputRect = new Rect(0, 0, 255, 255);
Uri bitmapUri = new Uri(strFileName, UriKind.RelativeOrAbsolute);
BitmapDecoder bitmapDecoder = BitmapDecoder.Create(bitmapUri,
BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
bitmapDecoder = BitmapDecoder.Create(bitmapUri, BitmapCreateOptions.IgnoreImageCache, BitmapCacheOption.None);
BitmapSource inputFrame = (BitmapSource) bitmapDecoder.Frames[0];
BitmapSource source1 = (BitmapSource) new CroppedBitmap(inputFrame, inputRect);
DrawingVisual drawingVisual = new DrawingVisual();
using(DrawingContext drawingContext = drawingVisual.RenderOpen()) {
drawingContext.DrawRectangle(new SolidColorBrush(Color.FromArgb(255, 255, 255, 255)), null, outputRect);
drawingContext.DrawImage((ImageSource) source1, outputRect);
drawingContext.Close();
}
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap(255, 255, 96.0, 96.0, PixelFormats.Default);
renderTargetBitmap.Render((Visual) drawingVisual);
source1 = (BitmapSource) new FormatConvertedBitmap((BitmapSource) renderTargetBitmap, PixelFormats.Bgr24, (BitmapPalette) null, 0.0);
BitmapFrame frameToCache = BitmapFrame.Create(source1, (BitmapSource) null, null, (ReadOnlyCollection < ColorContext > ) null);
encoder.Frames.Add(frameToCache);
using(FileStream fileStream = new FileStream(strOutFileName, FileMode.Create)) {
encoder.Save((Stream) fileStream);
fileStream.Flush();
}
}
Any clues out there?
Microsoft has published an article where they state that they are aware of this problem and are working on a resolution. They also provide a workaround, basically to temporary remove the September 12, 2017, Security and Quality Rollup update.
See: https://support.microsoft.com/en-us/help/4043601/rendering-issues-after-the-september-12-2017-net-security-and-quality
Discussion continued on https://social.msdn.microsoft.com/Forums/vstudio/en-US/0f14f14c-5cd3-4505-9168-2ef9dc1f7031/kb-4041083-kb-4040973-has-broken-wpf-rendering-in-services?forum=wpf
Seems to be more than me having this issue.
For us in the end the recommended update from Microsoft KB4043767 solved this issue. This will be part of the October rollout (currently in Preview).
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);
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'm trying to generate a simple tiff image at runtime. This image consists of white background and image downloaded from remote server.
Here's the code I've written for reaching that goal:
const string url = "http://localhost/barcode.gif";
var size = new Size(794, 1123);
var drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
{
drawingContext.DrawRectangle(new SolidColorBrush(Colors.White), null, new Rect(size));
var image = new BitmapImage(new Uri(url));
drawingContext.DrawImage(image, new Rect(0, 0, 180, 120));
}
var targetBitmap = new RenderTargetBitmap((int)size.Width, (int)size.Height, 96, 96, PixelFormats.Default);
targetBitmap.Render(drawingVisual);
var convertedBitmap = new FormatConvertedBitmap(targetBitmap, PixelFormats.BlackWhite, null, 0);
var encoder = new TiffBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(convertedBitmap));
using (var fs = new FileStream("out.tif", FileMode.Create))
{
encoder.Save(fs);
}
The code works and generates "out.tif" file. But the output file has just a white background, without image received from remote server.
What can be the problem? I have tried the following code in a variety of ways, but everytime with no luck.
I found this reading about the FormatConvertedBitmap class. Maybe give it a shot
FormatConvertedBitmap newFormatedBitmapSource = new FormatConvertedBitmap();
// BitmapSource objects like FormatConvertedBitmap can only have their properties
// changed within a BeginInit/EndInit block.
newFormatedBitmapSource.BeginInit();
// Use the BitmapSource object defined above as the source for this new
// BitmapSource (chain the BitmapSource objects together).
newFormatedBitmapSource.Source = targetBitmap;
// Set the new format to BlackWhite.
newFormatedBitmapSource.DestinationFormat = PixelFormats.BlackWhite;
newFormatedBitmapSource.EndInit();
Is there a way to get a DrawingContext (or something similar) for a WriteableBitmap? I.e. something to allow you to call simple DrawLine/DrawRectangle/etc kinds of methods, rather than manipulate the raw pixels directly.
I found sixlettervariables' solution the most workable one. However, there's a "drawingContext.Close()" missing. According to MSDN, "A DrawingContext must be closed before its content can be rendered".
The result is the following utility function:
public static BitmapSource CreateBitmap(
int width, int height, double dpi, Action<DrawingContext> render)
{
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
render(drawingContext);
}
RenderTargetBitmap bitmap = new RenderTargetBitmap(
width, height, dpi, dpi, PixelFormats.Default);
bitmap.Render(drawingVisual);
return bitmap;
}
This can then easily be used like this:
BitmapSource image = ImageTools.CreateBitmap(
320, 240, 96,
drawingContext =>
{
drawingContext.DrawRectangle(
Brushes.Green, null, new Rect(50, 50, 200, 100));
drawingContext.DrawLine(
new Pen(Brushes.White, 2), new Point(0, 0), new Point(320, 240));
});
If you don't mind using System.Drawing you could do something like:
var wb = new WriteableBitmap( width, height, dpi, dpi,
PixelFormats.Pbgra32, null );
wb.Lock();
var bmp = new System.Drawing.Bitmap( wb.PixelWidth, wb.PixelHeight,
wb.BackBufferStride,
PixelFormat.Format32bppPArgb,
wb.BackBuffer );
Graphics g = System.Drawing.Graphics.FromImage( bmp ); // Good old Graphics
g.DrawLine( ... ); // etc...
// ...and finally:
g.Dispose();
bmp.Dispose();
wb.AddDirtyRect( ... );
wb.Unlock();
I'm wondering the same thing, as currently I do something like:
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
//
// ... draw on the drawingContext
//
RenderTargetBitmap bmp = new RenderTargetBitmap(width, height, dpi, dpi, PixelFormats.Default);
bmp.Render(drawingVisual);
image.Source = bmp;
}
I'm trying to use the WriteableBitmap to allow multithreaded access to the pixel buffer, which is currently not allowed with neither a DrawingContext nor a RenderTargetBitmap. Maybe some sort of WritePixels routine based off of what you've retrieved from the RenderTargetBitmap would work?
It appears the word is no.
For future reference, we plan to use a port of the Writeable Bitmap Extensions for WPF.
For a solution using purely existing code, any of the other suggestions mentioned below will work.
A different way to solve this problem is to use a RenderTargetBitmap as a backing store, just like in the WriteableBitmap example. Then you can create and issue WPF drawing commands to it whenever you want. For example:
// create the backing store in a constructor
var backingStore =
new RenderTargetBitmap(200,200,97,97,PixelFormats.Pbgra32);
myImage.Source = backingStore;
// whenever you want to update the bitmap, do:
var drawingVisual = new DrawingVisual();
var drawingContext = drawingVisual.RenderOpen();
{
// your drawing commands go here
drawingContext.DrawRectangle(
Brushes.Red, new Pen(),
new Rect(this.RenderSize));
}
Render(drawingContext);
drawingContext.Close();
backingStore.Render(drawingVisual);
If you want to redraw this RenderTargetBitmap every frame, you can catch the CompositionTarget.Rendering event, like this:
CompositionTarget.Rendering += MyRenderingHandler;