1、先来看看InkCanvas的一般用法:

<InkCanvas>
     <InkCanvas.DefaultDrawingAttributes>
           <DrawingAttributes StylusTip="Ellipse" Height="8" Width="4" IgnorePressure="False" FitToCurve="True" >
           <!--稍微变换一下,就算设备不支持“压感”,效果也是棒棒-->

    <DrawingAttributes.StylusTipTransform>
                   <Matrix M11="1" M12="1.5" M21="2.2" M22="1"/>
              </DrawingAttributes.StylusTipTransform>
           </DrawingAttributes>
     </InkCanvas.DefaultDrawingAttributes>
</InkCanvas>

2、自定义InkCanvas,实现毛笔效果

找到2篇文章,代码基本一致,也不知道哪位是原作者抑或都不是原作者?

使用WPF的自定义InkCanvas实现毛笔效果 【个人觉得该作者为原创?】

wpf inkcanvas customink 毛笔效果 【这位童鞋的话,后面都叛变去搞Unity3D了!】

以上代码缺点:

2-1、卡顿【虽然提到了解决办法,但都没有给出具体代码】

2-2、颜色【毛笔配黑墨才是正途,但世界是多姿多彩的不是?】

2-3、粗细【这个嘛~】

废话不多说,奉上改进后的代码:

  public class ChinesebrushRenderer : DynamicRenderer
{
private ImageSource imageSource;
private readonly double width = ; protected override void OnDrawingAttributesReplaced()
{
if (DesignerProperties.GetIsInDesignMode(this.Element))
return; base.OnDrawingAttributesReplaced(); var dv = new DrawingVisual();
var size = ;
using (var conext = dv.RenderOpen())
{
//[关键]OpacityMask了解下?也许有童鞋想到的办法是,各种颜色的图片来一张?
conext.PushOpacityMask(new ImageBrush(new BitmapImage(new Uri(AppDomain.CurrentDomain.BaseDirectory + "Images\\pen.png", UriKind.Absolute))));
//用颜色生成画笔画一个矩形
conext.DrawRectangle(new SolidColorBrush(this.DrawingAttributes.Color), null, new Rect(, , size, size));
conext.Close();
}
var rtb = new RenderTargetBitmap(size, size, 96d, 96d, PixelFormats.Pbgra32);
rtb.Render(dv);
imageSource = BitmapFrame.Create(rtb);
//[重要]此乃解决卡顿问题的关键!
imageSource.Freeze();
} protected override void OnDraw(DrawingContext drawingContext, StylusPointCollection stylusPoints, Geometry geometry, Brush fillBrush)
{
var p1 = new Point(double.NegativeInfinity, double.NegativeInfinity);
var p2 = new Point(, );
var w1 = this.width + ; for (int i = ; i < stylusPoints.Count; i++)
{
p2 = (Point)stylusPoints[i]; //两点相减得到一个向量[高中数学知识了解下?]
var vector = p1 - p2; //得到 x、y的变化值
var dx = (p2.X - p1.X) / vector.Length;
var dy = (p2.Y - p1.Y) / vector.Length; var w2 = this.width;
if (w1 - vector.Length > this.width)
w2 = w1 - vector.Length; //为啥又来一个for?图像重叠,实现笔画的连续性,感兴趣的童鞋可以把for取消掉看看效果
for (int j = ; j < vector.Length; j++)
{
var x = p2.X;
var y = p2.Y; if (!double.IsInfinity(p1.X) && !double.IsInfinity(p1.Y))
{
x = p1.X + dx;
y = p1.Y + dy;
} //画图,没啥可说的
drawingContext.DrawImage(imageSource, new Rect(x - w2 / 2.0, y - w2 / 2.0, w2, w2)); //再把新的坐标赋值给p1,以序后来
p1 = new Point(x, y); if (double.IsInfinity(vector.Length))
break; }
}
}
 public class ChinesebrushStroke : Stroke
{ private ImageSource imageSource;
private readonly double width = ; public ChinesebrushStroke(StylusPointCollection stylusPointCollection, Color color) : base(stylusPointCollection)
{
if (DesignerProperties.GetIsInDesignMode(App.Current.MainWindow))
return;
var dv = new DrawingVisual();
var size = ;
using (var conext = dv.RenderOpen())
{
conext.PushOpacityMask(new ImageBrush(new BitmapImage(new Uri(AppDomain.CurrentDomain.BaseDirectory + "Images\\pen.png", UriKind.Absolute))));
conext.DrawRectangle(new SolidColorBrush(color), null, new Rect(, , size, size));
conext.Close();
}
var rtb = new RenderTargetBitmap(size, size, 96d, 96d, PixelFormats.Pbgra32);
rtb.Render(dv);
imageSource = BitmapFrame.Create(rtb); //Freezable 类提供特殊功能,以便在使用修改或复制开销很大的对象时帮助提高应用程序性能
//WPF中的Frozen(冻结)与线程及其他相关问题
imageSource.Freeze();
} //卡顿就是该函数造成,每写完一笔就会调用,当笔画过长,后果可想而知~
protected override void DrawCore(DrawingContext drawingContext, DrawingAttributes drawingAttributes)
{
if (this.StylusPoints?.Count < )
return; var p1 = new Point(double.NegativeInfinity, double.NegativeInfinity);
var w1 = this.width + ; for (int i = ; i < StylusPoints.Count; i++)
{
var p2 = (Point)this.StylusPoints[i]; var vector = p1 - p2; var dx = (p2.X - p1.X) / vector.Length;
var dy = (p2.Y - p1.Y) / vector.Length; var w2 = this.width;
if (w1 - vector.Length > this.width)
w2 = w1 - vector.Length; for (int j = ; j < vector.Length; j++)
{
var x = p2.X;
var y = p2.Y; if (!double.IsInfinity(p1.X) && !double.IsInfinity(p1.Y))
{
x = p1.X + dx;
y = p1.Y + dy;
} drawingContext.DrawImage(imageSource, new Rect(x - w2 / 2.0, y - w2 / 2.0, w2, w2)); p1 = new Point(x, y); if (double.IsInfinity(vector.Length))
break;
}
}
}
}
 public class ChinesebrushCanvas : InkCanvas
{
public ChinesebrushCanvas()
{
//当然要换上我们特地搞出来的ChinesebrushRenderer
this.DynamicRenderer = new ChinesebrushRenderer();
} protected override void OnStrokeCollected(InkCanvasStrokeCollectedEventArgs e)
{
//感兴趣的童鞋,注释这一句看看?
this.Strokes.Remove(e.Stroke); this.Strokes.Add(new ChinesebrushStroke(e.Stroke.StylusPoints, this.DefaultDrawingAttributes.Color));
}
}

笔画原图:

以上代码只是解决了1、2点,第三点嘛~毕竟每个人都有自己的粗细,大家自行体会~

吐槽一下:

本以为本篇文章能有点小小贡献,于是发布到“首页”,结果也就存活十多分钟,而且园内搜索还搜不到!

其实这个需求很久以前做项目就有提了,但那时候刚出来工作没多久【12年毕业,工作以后自学WPF】,还是一个菜鸟萌新,一篇相关文章都搜索不到啊不到!【手动哭泣】

之后也陆陆续续做了好多类似项目,但一直使用文中第一种方案,效果也能被客户接受。

哎,期待有缘人吧!毕竟WPF用的人还是太少!

也是,本篇文章没得个“抄袭”的罪名算好的了,还胆大包天的贴出原文链接!

最后【手动满地打滚撒泼~】

WPF 毛笔字的更多相关文章

  1. 在WPF中使用依赖注入的方式创建视图

    在WPF中使用依赖注入的方式创建视图 0x00 问题的产生 互联网时代桌面开发真是越来越少了,很多应用都转到了浏览器端和移动智能终端,相应的软件开发上的新技术应用到桌面开发的文章也很少.我之前主要做W ...

  2. MVVM框架从WPF移植到UWP遇到的问题和解决方法

    MVVM框架从WPF移植到UWP遇到的问题和解决方法 0x00 起因 这几天开始学习UWP了,之前有WPF经验,所以总体感觉还可以,看了一些基础概念和主题,写了几个测试程序,突然想起来了前一段时间在W ...

  3. MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息

    MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二 ...

  4. MVVM模式解析和在WPF中的实现(五)View和ViewModel的通信

    MVVM模式解析和在WPF中的实现(五) View和ViewModel的通信 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 M ...

  5. MVVM设计模式和WPF中的实现(四)事件绑定

    MVVM设计模式和在WPF中的实现(四) 事件绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  6. MVVM模式解析和在WPF中的实现(三)命令绑定

    MVVM模式解析和在WPF中的实现(三) 命令绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  7. MVVM模式和在WPF中的实现(二)数据绑定

    MVVM模式解析和在WPF中的实现(二) 数据绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  8. MVVM模式和在WPF中的实现(一)MVVM模式简介

    MVVM模式解析和在WPF中的实现(一) MVVM模式简介 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在 ...

  9. 逆天通用水印支持Winform,WPF,Web,WP,Win10。支持位置选择(9个位置 ==》[X])

    常用技能:http://www.cnblogs.com/dunitian/p/4822808.html#skill 逆天博客:http://dnt.dkil.net 逆天通用水印扩展篇~新增剪贴板系列 ...

随机推荐

  1. SpriteBuilder中CCB精灵对象的Sprite frame为什么有时候不能修改

    有时候你会发现CCB中的精灵对象(root节点)的Sprite frame是灰色的,不能修改.因为它是根对象,所以不存在被嵌入其他CCB的情况,那到底是什么原因呢? 可以发现此时的Timeline当前 ...

  2. SpriteBuilder中如何平均拉伸精灵帧动画的距离

    首先要在Timeline中选中所有的精灵帧,可以通过如下2种的任意一种办法达成: 1按下Shift键的同时鼠标单击它们 2鼠标在Timeline空白区拖拽直到拉出的矩形包围住所有精灵帧方块后放开鼠标. ...

  3. TCP的核心系列 — SACK和DSACK的实现(六)

    上篇文章中我们主要说明如何skip到一个SACK块对应的开始段,如何walk这个SACK块包含的段,而没有涉及到 如何标志一个段的记分牌.37版本把给一个段打标志的内容独立出来,这就是tcp_sack ...

  4. Linux常用命令(第二版) --系统开关机命令

    系统开关机命令 说明-服务器不会经常的关机,重启,没有故障,服务器不会关机.因此这些命令就显得不是很常用. 1.shutdown /usr/sbin/shutdown e.g. shutdown -h ...

  5. mahout系列之---谱聚类

    1.构造亲和矩阵W 2.构造度矩阵D 3.拉普拉斯矩阵L 4.计算L矩阵的第二小特征值(谱)对应的特征向量Fiedler 向量 5.以Fiedler向量作为kmean聚类的初始中心,用kmeans聚类 ...

  6. mybatis ---- 级联查询 一对多 (集合映射)

    关联有嵌套查询和嵌套结果两种方式,本文是按照嵌套结果这种方式来说明的 上一章介绍了多对一的关系,用到了<association></association>,这是一个复杂类型的 ...

  7. [Zabbix3.0 ]添加Nginx监控

    通过Nginx的http_stub_status_module模块提供的状态信息来监控,所以在Agent端需要配置Nginx状态获取的脚本,和添加key信息等,然后在Server端配置Nginx的监控 ...

  8. java原子操作

    一.何谓Atomic? Atomic一词跟原子有点关系,后者曾被人认为是最小物质的单位.计算机中的Atomic是指不能分割成若干部分的意思.如果一段代码被认为是Atomic,则表示这段代码在执行过程中 ...

  9. SQL语言逻辑执行顺序

    SQL语言逻辑执行顺序 2012-12-18 16:18:13 分类: 数据库开发技术 查询的逻辑执行顺序 FROM < left_table> ON < join_conditio ...

  10. 关于mybatis更新数据的问题

    前两天用mybatis的时候,发现这样一个问题,日志显示mytatis更新数据已经成功了,但是实际上数据库是没有更新到的,经过一番查找,发现mybatis更新的时候默认返回的是查找到的数据(Rows  ...