I'm having problem to get consistent scaling in my WinForms application that uses a Metafile with millimeter as the unit of measure. I wrote a small sample application to illustrate the problem.
This is how the application looks on a Windows 7 desktop machine:
This is how the application looks on a Windows 8 laptop machine:
The source code:
private void MainForm_Paint(object sender, PaintEventArgs e)
{
var g = e.Graphics;
var blueBrush = new SolidBrush(Color.Blue);
var bluePen = new Pen(blueBrush);
g.DrawRectangle(bluePen, 0, 0, 200, 200);
g.DrawLine(bluePen, 100, 0, 100, 200);
g.DrawLine(bluePen, 0, 100, 200, 100);
g.DrawString(g.DpiX+" dpi", new Font("Arial", 10), blueBrush, 0, 205);
Metafile metafile;
var size = new Size(200, 200);
using (var stream = new MemoryStream())
{
using (Graphics offScreenBufferGraphics = Graphics.FromHwndInternal(IntPtr.Zero))
{
IntPtr deviceContextHandle = offScreenBufferGraphics.GetHdc();
metafile = new Metafile(stream, deviceContextHandle, new RectangleF(0, 0, size.Width, size.Height), MetafileFrameUnit.Millimeter, EmfType.EmfPlusOnly);
offScreenBufferGraphics.ReleaseHdc();
using (Graphics mg = Graphics.FromImage(metafile))
{
mg.PageUnit = GraphicsUnit.Millimeter;
var redPen = new Pen(new SolidBrush(Color.Red));
const float scaleFactor = 0.75f;
mg.ScaleTransform(scaleFactor, scaleFactor);
mg.DrawLine(redPen, 0, 0, 200, 200);
mg.DrawLine(redPen, 0, 200, 200, 0);
}
}
}
g.DrawImage(metafile, 0, 0, 200, 200);
}
Both machines are set on 96dpi, yet the Win8 machine renders the metafile (the red cross) smaller.
The scale factor 0.75 is calculated from the difference between the standard 72 dpi and the current 96 dpi, 72/96=0.75, is this correct? Edit: See answer below why this will not work.
But mostly, why is it scaled differently on the Win8 machine and what setting can I fetch to compensate? Seems like the Win8 machine needs a scale factor around 1.25 to make the red cross align with the blue rectangle.
Thanks!
Found the answer myself after reading this code project article. Turns out that the screen size on the machine (1920x1200 on desktop and 1600x900 on laptop) affect the resolution of the metafile. The assumption of 72dpi that was used to calculate the scale factor 0.75 was somewhat correct on my desktop machine, but not on the Win8 laptop.
The metafile resolution can be fetched from the metafile header, and then used to calculate the correct scaling factor:
var metafileHeader = metafile.GetMetafileHeader();
float sx = metafileHeader.DpiX/g.DpiX;
float sy = metafileHeader.DpiY/g.DpiY;
mg.ScaleTransform(sx, sy);
The complete code can be found here.
Then I get correct scaling on both machines:
Related
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:
i am trying to use glass magnifier on PictureEdit(devexpress) control in windows form
we have set PictureEdit.SizeMode =Squeeze . its important. in our application i am already using Squeeze size mode.
i am getting problem of calculation of mouse location , for draw rect image onto enalged panel.
here is code of PartialMag_Paint event of enalaged panel. in which we show zoomed imaged of mouse pointed area
int srcx = (PartailMagImageView.Location.X + PartailMagImageView.Width / 2);
int srcy = (PartailMagImageView.Location.Y + PartailMagImageView.Height / 2);
e.Graphics.DrawImage(this.Image
, new System.Drawing.Rectangle(0, 0, PartailMagImageView.Width, PartailMagImageView.Height)
, new System.Drawing.Rectangle(srcx - ZoomOutRate / 2, srcy - ZoomOutRate / 2, ZoomOutRate, ZoomOutRate)
, GraphicsUnit.Pixel);
Pen mypen = new Pen(Color.Black, 7);
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
e.Graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
e.Graphics.DrawEllipse(mypen, 0, 0, PartailMagImageView.Width - 7, PartailMagImageView.Height - 7);
if any one need to see complete code sample. i am attaching sample in dropbox link
https://www.dropbox.com/s/mogmshuiimtvhk7/ImageMagnifyingWindowsForm.zip?dl=0
PictureEdit has the ViewportToImage and ImageToViewport methods allowing you to convert viewport coordinates to the source image coordinates and vice versa.
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).
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;
I suppose it's some king of gradient used, but I can't tell.
I'd like to use the same in my WinForms application.
Easiest way to do something like this is to use a program like Paint.Net to get the RGB values of the colors used in the drawing.
Using a panel as an example:
void panel1_Paint(object sender, PaintEventArgs e) {
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (LinearGradientBrush br = new LinearGradientBrush(
panel1.ClientRectangle,
Color.FromArgb(52, 151, 254),
Color.FromArgb(61, 129, 243),
LinearGradientMode.Vertical)) {
e.Graphics.FillRectangle(br, panel1.ClientRectangle);
}
using (Pen p = new Pen(Color.FromArgb(37, 110, 184), 2)) {
e.Graphics.DrawRectangle(p, 0, 0,
panel1.ClientSize.Width - 1,
panel1.ClientSize.Height - 1);
}
}
Results in:
Pretty close to the original. You can make adjustments by lightening or darkening the colors used.