使用MeasureCharacterRanges在图片绘制多色文本时位置异常问题
问题原因分析
直接从缩放图片创建Graphics时出现文本位置扭曲,核心原因是GDI+与GDI的DPI处理逻辑不兼容:
MeasureCharacterRanges属于GDI+ API,计算结果完全依赖当前Graphics对象的DPI设置;TextRenderer.DrawText是GDI API的封装,默认使用系统屏幕DPI进行渲染,而非当前Graphics的DPI。
当你直接从缩放图片创建Graphics时,其DPI通常与屏幕DPI不一致(比如图片DPI为96,高分辨率屏幕DPI为144),导致两者的坐标计算出现偏差,最终文本位置扭曲。而中间Bitmap的Graphics对象,默认会继承Bitmap的DPI(或创建时显式匹配了DPI),让GDI+和GDI的DPI设置对齐,因此渲染正常。
解决方案
方案1:统一Graphics与TextRenderer的DPI设置
显式将Graphics的DPI设置为与TextRenderer一致(通常是屏幕DPI),同时强制TextRenderer使用当前Graphics的DPI:
private void AddText1(string txt, Graphics g, Rectangle rect) { // 获取屏幕DPI并同步到当前Graphics using (Graphics screenG = Graphics.FromHwnd(IntPtr.Zero)) { g.DpiX = screenG.DpiX; g.DpiY = screenG.DpiY; } TextFormatFlags flags = TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.NoPadding | TextFormatFlags.NoClipping; using (StringFormat format = new StringFormat()) { format.Alignment = StringAlignment.Center; format.LineAlignment = StringAlignment.Center; MatchCollection mc = Regex.Matches(txt, @"[^\s]+"); CharacterRange[] ranges = mc.Cast<Match>().Select(m => new CharacterRange(m.Index, m.Length)).ToArray(); format.SetMeasurableCharacterRanges(ranges); using (Font font = new Font("Times New Roman", 40, FontStyle.Regular, GraphicsUnit.Point)) { Region[] regions = g.MeasureCharacterRanges(txt, font, rect, format); for (int i = 0; i < ranges.Length; i++) { Rectangle WordBounds = Rectangle.Round(regions[i].GetBounds(g)); string word = txt.Substring(ranges[i].First, ranges[i].Length); // 强制TextRenderer遵循当前Graphics的变换和裁剪规则 TextFormatFlags dpiAwareFlags = flags | TextFormatFlags.PreserveGraphicsClipping | TextFormatFlags.PreserveGraphicsTranslateTransform; if (word == "keyword") TextRenderer.DrawText(g, word, font, WordBounds, Color.YellowGreen, dpiAwareFlags); else TextRenderer.DrawText(g, word, font, WordBounds, Color.White, dpiAwareFlags); } } } }
方案2:统一使用GDI+ API渲染
放弃TextRenderer.DrawText,改用GDI+的Graphics.DrawString,避免跨API的DPI差异:
private void AddText1(string txt, Graphics g, Rectangle rect) { using (StringFormat format = new StringFormat()) { format.Alignment = StringAlignment.Center; format.LineAlignment = StringAlignment.Center; MatchCollection mc = Regex.Matches(txt, @"[^\s]+"); CharacterRange[] ranges = mc.Cast<Match>().Select(m => new CharacterRange(m.Index, m.Length)).ToArray(); format.SetMeasurableCharacterRanges(ranges); using (Font font = new Font("Times New Roman", 40, FontStyle.Regular, GraphicsUnit.Point)) { Region[] regions = g.MeasureCharacterRanges(txt, font, rect, format); for (int i = 0; i < ranges.Length; i++) { RectangleF WordBounds = regions[i].GetBounds(g); string word = txt.Substring(ranges[i].First, ranges[i].Length); using (SolidBrush brush = new SolidBrush(word == "keyword" ? Color.YellowGreen : Color.White)) { g.DrawString(word, font, brush, WordBounds, format); } } } } }
方案3:创建Graphics时显式匹配目标图片DPI
如果目标图片有特定DPI,创建Graphics时直接设置对应参数:
// 示例:从图片创建Graphics时同步DPI Bitmap targetImage = new Bitmap("scaled-image.png"); using (Graphics g = Graphics.FromImage(targetImage)) { g.DpiX = targetImage.HorizontalResolution; g.DpiY = targetImage.VerticalResolution; AddText1("Hello keyword world", g, new Rectangle(0, 0, targetImage.Width, targetImage.Height)); targetImage.Save("output.png"); }
内容的提问来源于stack exchange,提问作者frek
相关产品推荐
相关产品推荐

