我只是无法在DPI为120的笔记本电脑显示器上使用OnRender
在drawingContext.DrawLine()
中画一条像素宽的黑色(!)线。多年来,我一直遇到这个问题,并且阅读这里有许多关于stackoverflow的答案,但我根本无法获得有效的代码。许多答案链接到本文:wpftutorial.net: Draw On Physical Device Pixels。建议使用准则并将其偏移半个像素,即设置y = 10.5而不是96 DPI监视器的期望位置10。但这并未说明如何针对不同的DPI进行计算。
WPF使用1/96英寸宽的逻辑单元(LU),这意味着drawingContext.DrawLine(Width:1)希望绘制一条1/96英寸宽的线。我的显示器的像素为1/120英寸宽。
文章仅说120 DPI的线宽不应为1 LU,而应为0.8 LU,即1/120英寸,即我显示器的像素宽度。
因此,偏移量应该是0.4而不是0.5吗?这使得整个坐标计算非常复杂。假设我要在5 LU上画一条线,即5/96英寸在0.0520英寸处。最近的监视器像素将是6英寸(0.05英寸)。意味着校正将需要为0.25 LU。但是,如果我想在6 LU处画一条线,则偏移量必须为0.5。
我可以立即看到的一个问题是我的代码无法确定坐标0、0恰好位于实际的显示器像素上。如果父控件将我的控件放置在不均匀的LU上,则0,0不会与监视器像素精确对齐,这意味着我没有机会计算特定LU的偏移量。
所以我想,我到底想画10条线,每条线将y增加0.1。结果(第二行)如下所示:
第一行显示了完美的1像素宽的黑线的外观,放大了8倍。第二行显示WPF绘制的线条:
Pen penB08 = new Pen(Brushes.Black, 0.8);
for (int i = 0; i < 10; i++) {
drawingContext.DrawLine(penB08, new Point(i * 9, i*0.1), new Point(i * 9 + 5, i*0.1));
}
您可以说,没有一个是正好1像素宽,因此它们都不是真正的黑色!
如果上述文章正确,则至少一行应正确显示。原因:96 DPI与120 DPI之差为1/5。这意味着第5个LU像素应从与监视像素完全相同的位置开始。偏移量应该是1/2 LU,这就是为什么我做了10 1/10步。
有关该文章中建议的使用指南的其他示例,请参见下文。如该文章所述,如果使用准则或使用偏移量没有什么区别。
请提供代码,该代码可在120 DPI显示器上真正画出1像素宽的线。我知道,关于stackoverflow的答案已经很多,它们解释了理论上应该如何解决。另请注意,该代码必须使用OnRender
在drawingContext.DrawLine()
中运行,而不是使用Visual
来运行,SnapToDevicePixels
具有诸如RenderOptions.EdgeMode="Aliased"
之类的属性来解决该问题。
我知道重复的问题对stackoverflow不利。但是通常一个问题被标记为重复,这实际上是不同的,因而阻止了该问题的讨论。因此,如果您认为已经有了答案,请发表评论,并给我机会进行检查。然后,我将运行该代码并将其放大。只有这样才能知道它是否真的有效。
我已经花了一周的时间尝试各种变体来获得线条。没有人给出黑色的1个监视器像素线。全部为灰色,且宽度超过1像素。
克莱门斯建议使用SnapsToDevicePixels="True"
和/或SnapsToDevicePixels = true;
RenderOptions.SetEdgeMode((DependencyObject)this, EdgeMode.Aliased);
。这是没有选项或它们的任何组合的结果:
SnapsToDevicePixels
似乎SetEdgeMode
没有任何作用,但是对于paint.net
,前3个破折号比后面的7个破折号高1个像素,这意味着出现了混叠现象,但是行仍然是2或甚至3像素宽,而且黑色也不正确。
我在using System;
using System.Windows;
using System.Windows.Media;
namespace Sample {
public partial class MainWindow: Window {
GlyphDrawer glyphDrawerNormal;
GlyphDrawer glyphDrawerBold;
public MainWindow() {
InitializeComponent();
Background = Brushes.Transparent;
var dpi = VisualTreeHelper.GetDpi(this);
glyphDrawerNormal = new GlyphDrawer(FontFamily, FontStyle, FontWeight, FontStretch, dpi.PixelsPerDip);
glyphDrawerBold = new GlyphDrawer(FontFamily, FontStyle, FontWeights.Bold, FontStretch, dpi.PixelsPerDip);
}
protected override void OnRender(DrawingContext drawingContext) {
drawingContext.DrawRectangle(Brushes.White, null, new Rect(0, 0, Width, Height));
drawSampleLines(drawingContext);
}
Pen penB1 = new Pen(Brushes.Black, 1);
Pen penB08 = new Pen(Brushes.Black, 0.8);
Pen penB05 = new Pen(Brushes.Black, 0.5);
const double x0 = 10.0;
const double x1 = 300.0; //line start
const double x2 = 305.0;
const int ySpacing = 18;
const int lineOffset = -7;
private void drawSampleLines(DrawingContext drawingContext) {
var y = 30.0;
glyphDrawerNormal.Write(drawingContext, new Point(x0, y), "Perfect, 1 pix y step", FontSize, Brushes.Black);
y += 2*ySpacing;
glyphDrawerBold.Write(drawingContext, new Point(x0, y), "Line samples with 1 logical unit width pen", FontSize, Brushes.Black);
y += ySpacing;
drawLineSet(drawingContext, ref y, penB1);
y += 2*ySpacing;
glyphDrawerBold.Write(drawingContext, new Point(x0, y), "Line samples with 0.8 logical unit width pen", FontSize, Brushes.Black);
y += ySpacing;
drawLineSet(drawingContext, ref y, penB08);
y += 2*ySpacing;
glyphDrawerBold.Write(drawingContext, new Point(x0, y), "Line samples with 0.5 logical unit width pen", FontSize, Brushes.Black);
y += ySpacing;
drawLineSet(drawingContext, ref y, penB05);
}
private void drawLineSet(DrawingContext drawingContext, ref double y, Pen pen) {
var yL = y + lineOffset;
glyphDrawerNormal.Write(drawingContext, new Point(x0, y), "Plain, 1 pix y step", FontSize, Brushes.Black);
for (int i = 0; i < 10; i++) {
drawingContext.DrawLine(pen, new Point(x1 + i * 9, yL+i), new Point(x2 + i * 9, yL+i));
}
y += ySpacing; yL += ySpacing;
glyphDrawerNormal.Write(drawingContext, new Point(x0, y), "Plain, 1 pix y step, 0.5 pix x offset", FontSize, Brushes.Black);
for (int i = 0; i < 10; i++) {
drawingContext.DrawLine(pen, new Point(x1 + i * 9 + .5, yL+i + 0.5), new Point(x2 + i * 9, yL+i));
}
y += ySpacing; yL += ySpacing;
glyphDrawerNormal.Write(drawingContext, new Point(x0, y), "Plain, 0.5 pix y step", FontSize, Brushes.Black);
for (int i = 0; i < 10; i++) {
drawingContext.DrawLine(pen, new Point(x1 + i * 9, yL+i/2.0), new Point(x2 + i * 9, yL+i/2.0));
}
y += ySpacing; yL += ySpacing;
glyphDrawerNormal.Write(drawingContext, new Point(x0, y), "with Guidelines, 1 pix y step", FontSize, Brushes.Black);
for (int i = 0; i < 10; i++) {
var xLeft = x1 + i * 9;
var xRight = x2 + i * 9;
var yLine = yL + i;
GuidelineSet guidelines = new GuidelineSet();
guidelines.GuidelinesX.Add(xLeft);
guidelines.GuidelinesX.Add(xRight);
guidelines.GuidelinesY.Add(yLine);
drawingContext.PushGuidelineSet(guidelines);
drawingContext.DrawLine(pen, new Point(xLeft, yLine), new Point(xRight, yLine));
drawingContext.Pop();
}
y += ySpacing; yL += ySpacing;
glyphDrawerNormal.Write(drawingContext, new Point(x0, y), "with Guidelines and 0.5 pix y offset, 1 pix y step", FontSize, Brushes.Black);
for (int i = 0; i < 10; i++) {
var xLeft = x1 + i * 9;
var xRight = x2 + i * 9;
var yLine = yL + i;
GuidelineSet guidelines = new GuidelineSet();
guidelines.GuidelinesX.Add(xLeft);
guidelines.GuidelinesX.Add(xRight);
guidelines.GuidelinesY.Add(yLine + 0.5);
drawingContext.PushGuidelineSet(guidelines);
drawingContext.DrawLine(pen, new Point(xLeft, yLine), new Point(xRight, yLine));
drawingContext.Pop();
}
y += ySpacing; yL += ySpacing;
glyphDrawerNormal.Write(drawingContext, new Point(x0, y), "with Guidelines and 0.5 pix x offset, 1 pix y step", FontSize, Brushes.Black);
for (int i = 0; i < 10; i++) {
var xLeft = x1 + i * 9;
var xRight = x2 + i * 9;
var yLine = yL + i;
GuidelineSet guidelines = new GuidelineSet();
guidelines.GuidelinesX.Add(xLeft + 0.5);
guidelines.GuidelinesX.Add(xRight + 0.5);
guidelines.GuidelinesY.Add(yLine);
drawingContext.PushGuidelineSet(guidelines);
drawingContext.DrawLine(pen, new Point(xLeft, yLine), new Point(xRight, yLine));
drawingContext.Pop();
}
}
}
/// <summary>
/// Draws glyphs to a DrawingContext. From the font information in the constructor, GlyphDrawer creates and stores the GlyphTypeface, which
/// is used everytime for the drawing of the string.
/// </summary>
public class GlyphDrawer {
Typeface typeface;
public GlyphTypeface GlyphTypeface {
get { return glyphTypeface; }
}
GlyphTypeface glyphTypeface;
public float PixelsPerDip { get; }
public GlyphDrawer(FontFamily fontFamily, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, double pixelsPerDip) {
typeface = new Typeface(fontFamily, fontStyle, fontWeight, fontStretch);
if (!typeface.TryGetGlyphTypeface(out glyphTypeface))
throw new InvalidOperationException("No glyphtypeface found");
PixelsPerDip = (float)pixelsPerDip;
}
/// <summary>
/// Writes a string to a DrawingContext, using the GlyphTypeface stored in the GlyphDrawer.
/// </summary>
/// <param name="drawingContext"></param>
/// <param name="origin"></param>
/// <param name="text"></param>
/// <param name="size">same unit like FontSize: (em)</param>
/// <param name="brush"></param>
public void Write(DrawingContext drawingContext, Point origin, string text, double size, Brush brush) {
if (string.IsNullOrEmpty(text)) return;
ushort[] glyphIndexes = new ushort[text.Length];
double[] advanceWidths = new double[text.Length];
double totalWidth = 0;
for (int charIndex = 0; charIndex<text.Length; charIndex++) {
ushort glyphIndex = glyphTypeface.CharacterToGlyphMap[text[charIndex]];
glyphIndexes[charIndex] = glyphIndex;
double width = glyphTypeface.AdvanceWidths[glyphIndex] * size;
advanceWidths[charIndex] = width;
totalWidth += width;
}
GlyphRun glyphRun = new GlyphRun(glyphTypeface, 0, false, size, PixelsPerDip, glyphIndexes, origin, advanceWidths, null, null, null, null, null, null);
drawingContext.DrawGlyphRun(brush, glyphRun);
}
}
}
中绘制的第一行是作为参考,以显示适当的行应为什么样。这是生成行的代码:
@PostMapping(path = "/test", consumes = "application/json")
public String test(@RequestBody User user) {
return user.toString();
}
@PostMapping(path = "/test", consumes = "application/x-www-form-urlencoded")
public String test(User user) {
return user.toString();
}
答案 0 :(得分:0)
我试图在DPI设置不是96的显示器上,在一种记谱应用程序(工作人员线,条形线,音符茎等)中获得更好的直线效果,并提出了以下代码。真的对我有用。
Matrix m = PresentationSource.FromVisual(this).CompositionTarget.TransformToDevice;
double dpiFactor = 1/m.M11;
ScaleTransform scale = New ScaleTransform(dpiFactor, dpiFactor);
dc.PushTransform(scale);
...
...
dc.Pop();