<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Twlm&apos;s Blog</title><description>Gatito ❤😺</description><link>https://blog.twlmgatito.com/</link><language>zh_CN</language><item><title>论文笔记 《Password-Based Credentials with Securiy Against Server Compromise》</title><link>https://blog.twlmgatito.com/posts/note-for-mkpbc/</link><guid isPermaLink="true">https://blog.twlmgatito.com/posts/note-for-mkpbc/</guid><pubDate>Thu, 20 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;原论文地址：&lt;a href=&quot;https://link.springer.com/chapter/10.1007/978-3-031-50594-2_8&quot;&gt;Password-Based Credentials with Security Against Server Compromise | SpringerLink&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;四个安全目标：&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;强不可伪造性：敌手A知道password并且服务器妥协，但不知道用户 ask (authenticated secret key)，无法伪造验证令牌&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在线不可伪造性：A知道ask，服务器未妥协，则A只能通过在线猜测password来伪造令牌&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;服务器可发现异常行为从而阻断&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;离线不可伪造性：A知道ask，服务器妥协，则A能使用离线暴力猜测password&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;使用强口令是最后一道防线&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;口令隐藏性：服务器储存的avk(针对multi-key PBC 在服务器端储存的用户独有的验证凭据)不会泄露关于password的任何信息&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;目的是对抗服务器妥协时泄露口令信息和服务器内部模拟用户&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;已有的方案&lt;/h1&gt;
&lt;p&gt;1. “Password-based authentication” 基于密码的身份验证: 不能应对服务器妥协和弱密码猜测&lt;/p&gt;
&lt;p&gt;2. “FIDO”: 高熵密钥(私钥)由用户拥有(通过加密硬件和软件管理，不便携)，服务器端只拥有用于验证的公钥(不会泄露用户信息)&lt;/p&gt;
&lt;p&gt;3. ZWY 框架下的single-key PBC&lt;/p&gt;
&lt;h1&gt;ZWY single-key PBC&lt;/h1&gt;
&lt;p&gt;“ZWY framework(skPBC)”: 安全等级近似于key-based方案，无需用户储存密钥信息；&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在注册时获取 cryptographically strong access credential，可以随意安置(via untrusted cloud providers or copied on low-security devices)&lt;/li&gt;
&lt;li&gt;在线不可伪造性(服务器未妥协的情况下)&lt;/li&gt;
&lt;li&gt;达不到强不可伪造性和离线不可伪造性&lt;/li&gt;
&lt;li&gt;服务器妥协失去所有安全性：无需恢复密钥即可模拟所有用户验证&lt;/li&gt;
&lt;li&gt;不需要Pw-Hiding&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;服务器拥有一个全局MAC密钥,用户凭证本质是服务器对uid生成的MAC密钥副本，注册时使用用户的password对MAC密钥进行对称加密。认证时，用户解密凭证恢复MAC密钥并与消息绑定后发送给服务器。(敌手可以让不诚实的用户注册后获取MAC密钥，但是此方案不考虑”corrupt users”的存在）&lt;/p&gt;
&lt;p&gt;敌手不知服务器的高熵MAC密钥，则无法验证解密后的值是否有效，从而确保在线不可伪造性。&lt;/p&gt;
&lt;h2&gt;skPBC Syntax&lt;/h2&gt;
&lt;p&gt;包括五个算法 “KGen, 〈RegU, RegS〉, Sign, Vf”，两个阶段“a registration phase and an authentication phase” ，两个参与方Server / User.&lt;/p&gt;
&lt;p&gt;Server: 长期单一密钥“KGen(λ) → (ssk, spk)”用于注册和验证所有用户，“ssk”是服务器的长期单一密钥，“spk”是与之对应的公钥&lt;/p&gt;
&lt;p&gt;注册阶段：用户:RegU(spk,uid,pw) → ask ; 服务器: 使用ssk签出ask并储存uid&lt;/p&gt;
&lt;p&gt;验证阶段：用户拟发送消息m，“Sign(uid, ask, pw, m) → τ”,将token τ和消息m发送给服务器，服务器执行“Vf(ssk, uid, m, τ ) → 0/1”&lt;/p&gt;
&lt;h2&gt;ZWY Framework的安全目标和威胁模型&lt;/h2&gt;
&lt;p&gt;选择消息和选择验证请求攻击下存在不可伪造性(“Existential Unforgeability under Chosen Message and Chosen Verification Queries Attack (EUF-CMVA)” (Dayanikli 和 Lehmann, 2024, p. 151))&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;经典的不可伪造：A只知道用户pw，对ask和server ssk一无所知&lt;/li&gt;
&lt;li&gt;达到在线不可伪造性，A知道用户ask&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Game of weak/strong UNF for A in skPBC(λ)：&lt;/h2&gt;
&lt;p&gt;现有用户列表&lt;code&gt;uid_1~n&lt;/code&gt;,及其对应&lt;code&gt;pw_1~n&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;敌手A可选择用户访问预言机&lt;code&gt;Sign&lt;/code&gt; \ &lt;code&gt;Vf&lt;/code&gt; 获取ask_i对挑战消息m进行签名和执行验证，请求过的(i,m)和i会被记录(分别在&lt;code&gt;Q&lt;/code&gt;和&lt;code&gt;RevCred&lt;/code&gt;中)&lt;/p&gt;
&lt;p&gt;A获胜条件：A给出uid_j (j∈[1,n],j∉RevCred,即合法的、从未请求过的用户)、新鲜的消息m*和签名τ*，如果验证通过则A获胜&lt;/p&gt;
&lt;p&gt;strongUNF实验中为A额外提供服务器私钥ssk&lt;/p&gt;
&lt;p&gt;Weak/Strong Unforgeability定义为对所有PPT敌手获胜以上实验的概率≤negl(λ)&lt;/p&gt;
&lt;h2&gt;skPBC达不到Strong(and Offline) Unforgeability&lt;/h2&gt;
&lt;p&gt;一旦敌手A得知服务器的密钥ssk，就能用自己选择的口令重新注册任意诚实的用户U，以获得有效的用户凭证，并以U的名义创建令牌。在skPBC方案下，敌手赢得strongUNF的概率为1。&lt;/p&gt;
&lt;h1&gt;mkPBC&lt;/h1&gt;
&lt;p&gt;skPBC不可能具有强不可伪造性，故引入多密钥的PBC。关键的区别在于，服务器不再具有单个密钥来颁发用户凭证和验证其令牌。而是为每个注册用户生成一个特定于用户的验证密钥，并在验证用户的令牌时使用该特定于用户的密钥。&lt;/p&gt;
&lt;h2&gt;Syntax&lt;/h2&gt;
&lt;p&gt;“A multi-key PBC scheme mkPBC = (Setup, 〈RegU, RegS〉, Sign, Vf)”&lt;/p&gt;
&lt;p&gt;Setup(λ)-&amp;gt;pp: 输出公共参数，作为其他算法的隐式输入&lt;/p&gt;
&lt;p&gt;〈RegU(uid, pw), RegS(uid)〉 → (ask; avk): 注册交互，用户输出凭证ask，服务器输出用户特定的验证密钥 avk&lt;/p&gt;
&lt;p&gt;Sign(uid,ask,pw,m)  → τ : 对消息m生成授权令牌&lt;/p&gt;
&lt;p&gt;Vf(uid, avk, m, τ ) → 0/1 : 验证有效性&lt;/p&gt;
&lt;h2&gt;Game of xUNF for A in mkPBC(λ) where x∈{strong,online,offline}&lt;/h2&gt;
&lt;p&gt;现有合法注册的用户uid,pw,ask,avk，敌手在特定条件下多轮访问特定预言机后，给出新的m*,τ*，若通过验证则敌手获胜&lt;/p&gt;
&lt;p&gt;条件如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;x=strong: 给予敌手avk和pw, 可访问Sign预言机(O_Sign会记录挑战消息m)&lt;/li&gt;
&lt;li&gt;x=online: 给予敌手ask, 可访问Sign和Vf预言机&lt;/li&gt;
&lt;li&gt;x=offline: 给予敌手ask, avk, 可访问Sign和TestPW预言机((pw’)=&amp;gt;pw==pw’)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Game of PW-Hiding for A in mkPBC(λ)&lt;/h2&gt;
&lt;p&gt;现有合法用户uid，均匀随机选取两个可能合法的pw: pw0和pw1，给予敌手avk(敌手此时只知道pw0和pw1,不知道具体选取的是哪一个)并访问Sign预言机(预言机知道选取的pw)，之后敌手输出选取的是哪一个pw，正确则敌手获胜。&lt;/p&gt;
&lt;h2&gt;mkPBC的安全定义&lt;/h2&gt;
&lt;p&gt;对于一个mkPBC方案和所有PPT敌手A：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“Strong Unforgeability” ：Pr[Game of strongUNF for A in mkPBC(λ)=1]≤negl(λ)&lt;/li&gt;
&lt;li&gt;“Online Unforgeability” ：Pr[Game of onlineUNF for A in mkPBC(λ)=1]≤(q_Vf+1)/|Dpw|+negl(λ)，q_Vf是猜测次数(即访问Vf预言机次数)，|Dpw|是口令域长度&lt;/li&gt;
&lt;li&gt;“Offline Unforgeability” ：Pr[Game of offlineUNF for A in mkPBC(λ)=1]≤qf/|Dpw|+negl(λ)，qf是请求TestPW预言机次数&lt;/li&gt;
&lt;li&gt;“Pw-Hiding” ：Pr[Game of PW-Hiding for A in mkPBC(λ)=1]≤1/2+negl(λ)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;构建Sign-Then-Encrypt的PBC方案&lt;/h2&gt;
&lt;p&gt;需要三个密码学原语：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;一个安全的伪随机函数PRF “F : {0, 1}^λ × X → Y”&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“安全的”意味着函数F(key, · )与同域的随机函数不可区分&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;一个达到&lt;code&gt;IND-CCA安全&lt;/code&gt;的非对称加密算法 ΠEnc:=(KGenE,Enc,Dec)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;一个达到&lt;code&gt;EUF-CMA安全&lt;/code&gt;的签名算法：ΠSign:=(SetupS,KGenS,SignS,VfS)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;要求实现“Complete Robustness”(CROB-Security)：即对于所有PPT敌手难以找到一对(消息,签名)能在两个不同的公钥下验证通过；&lt;/p&gt;
&lt;p&gt;以及“Randomness Injectivity” (RI)（可注入的随机性？可控的随机性？）： 所有PPT敌手难以找到对于两个不同的注入参数，使得KGen输出相同的公私钥；暗含：公钥和私钥唯一匹配，对于注入参数而言是确定性生成的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Scheme&lt;/h2&gt;
&lt;p&gt;一个基于签名后加密的多密钥PBC方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Setup(λ): pp ←SetupS(λ)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;User与Server注册阶段 RegU(uid,pw) 和 RegS(uid):&lt;/p&gt;
&lt;p&gt;U: 均匀随机选取PRF密钥k←{0,1}^λ，运行KGenS(pp,F(k,pw))生成签名公私钥pkSig和skSig;&lt;/p&gt;
&lt;p&gt;运行KGenE(λ)生成加密用的公私钥pkEnc和skEnc;&lt;/p&gt;
&lt;p&gt;发送(uid,avk:=(pkSig,skEnc))到S,输出ask:=(k,pkEnc);&lt;/p&gt;
&lt;p&gt;&lt;em&gt;签名和非对称加密的公私钥对都在客户端生成，发送签名公钥和加密私钥作为avk，保留PRF密钥k和加密公钥作为ask&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;S: 接收(uid,avk)储存即可，RegS输出avk&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sign(uid,ask,pw,m)&lt;/p&gt;
&lt;p&gt;运行σ←SignS(skSig,(uid,m)) 对消息和uid签名&lt;/p&gt;
&lt;p&gt;运行τ←Enc(pkEnc,σ)&lt;/p&gt;
&lt;p&gt;输出τ作为token&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Vf(uid,avk,m,τ)&lt;/p&gt;
&lt;p&gt;解密σ←Dec(skEnc,τ)，输出VfS(pkSig,(uid,m),σ)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Theorem &amp;amp; Proof&lt;/h2&gt;
&lt;h3&gt;1.如果F是安全的PRF并且ΠSign是EUF-CMA安全的签名方案，则该PBC是强不可伪造的&lt;/h3&gt;
&lt;p&gt;在服务器妥协(泄露avk)、用户pw泄露但是敌手不知道ask的情况下：敌手持有pkSig和skEnc，可解出令牌τ并得知其对应的(uid,m)；未知ask则无法运行F(k,pw)，就无法获得skSig。则可将问题归因到在未知skSig的情况下伪造签名，由于ΠSign是EUF-CMA安全，则不可行。即证明该PBC是强不可伪造的。&lt;/p&gt;
&lt;h3&gt;2. 如果F是随机预言机，ΠSign具有complete robustness和randomness injectivity，且ΠEnc具有CCA安全，则该PBC是在线不可伪造的&lt;/h3&gt;
&lt;p&gt;敌手具备 ask:=(k,pkEnc) 和 uid，未知avk:=(pkSign,skEnc)和pw。敌手通过猜测pw生成伪造的(pkSig’,skSig’):=KGenS(pp,F(k,pw’))和伪造签名σ’；由于ΠEnc具有CCA安全，则访问Sign预言机不会泄露任何关于签名σ的信息；由于未知pkSign则无法自行验证生成的签名，唯一的方法是访问Vf预言机。CROB确保Vf只会泄露pkSig的相等性，可注入随机性确保pkSig‘只能映射到单一pw猜测上，则敌手只能与Vf预言机一次猜测一次交互。至此该PBC是在线不可伪造的。&lt;/p&gt;
&lt;h3&gt;3.  如果F是随机预言机，ΠSign是EUF-CMA安全的，并且具有可注入随机性，则该PBC是离线不可伪造的&lt;/h3&gt;
&lt;p&gt;敌手具备ask,avk,uid,未知pw（作者在这里强调“具备ask”是指具备PRF密钥k，而不是签名私钥。注意到文中提及ask不储存而是即用即生成）。要伪造token τ则需要伪造skSig；此时敌手要么选择直接伪造签名(绕过skSig,但是ΠSign具有EUF-CMA安全则不可行)，要么通过离线暴力破解pw以生成skSig。且由于F是随机预言机、ΠSign有可注入随机性则只有唯一的pw能计算出正确的skSig。此时敌手的每一次猜测必须访问一次随机预言机F，则该PBC是离线不可伪造的。&lt;/p&gt;
&lt;h3&gt;4. 如果F是安全的PRF,则该PBC具有口令隐藏性&lt;/h3&gt;
&lt;p&gt;方案中唯一依赖pw的是生成签名公私钥之时敌手知道pkSig但未知k，获取的方式是与Sign预言机交互。由于k是随即均匀选取的，且通过安全的PRF转换，则无法区分pkSig是由r=F(k,pw_b)还是从随机预言机中选取(此时avk独立于pw存在)。&lt;/p&gt;
&lt;h1&gt;Appendix&lt;/h1&gt;
&lt;p&gt;“The DSA, Schnorr and BLS signature scheme all achieve randomness injectivity information-theoretically. DSA and Schnorr are CROBsecure assuming a collision-resistant hash function, and BLS is informationtheoretically CROB-secure.” (Dayanikli 和 Lehmann, 2024, p. 165) DSA、Schnorr 和 BLS 签名方案在理论上都实现了随机性注入性信息。DSA 和 Schnorr 是假设具有抗碰撞哈希函数的 CROB安全，而 BLS 在信息理论上是 CROB 安全的。&lt;/p&gt;
&lt;h1&gt;一些疑问&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;用户需要携带两个密钥（一个自己的password和一个PRF密钥k）牺牲了便携性。在具体的方案中，PRF应该使用流密码(e.g. AES)、HMAC或其他基于椭圆曲线构造的方案。其中某些方案并不支持所有k的长度，意味着要么选择HMAC这样支持所有长度的方案，然后让用户记住2个密钥，要么使用其他方案，k交由密钥管理器或安全芯片保管(没有FIDO的便捷性强，但是对保管方的安全性要求不高)。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“We therefore do not store (or even generate) the key normally, but derive it deterministically as (pkSig, skSig) := KGenS(pp; F (k, pw)) from a PRF key k and the user’s password pw. The user now only stores the PRF key k and re-derives the signature key pair when she wants to generate an authentication token.” (Dayanikli 和 Lehmann, 2024, p. 158)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;设想一个简单的“用户名+密码(口令)”授权登录的方式：服务器储存用户id和对应的口令加盐哈希，用户端请求注册和登录时提交口令明文的哈希（避免明文网络传输），在服务器内部对比后生成授权：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不满足强不可伪造和离线不可伪造，服务器妥协时(泄露用户信息表，加盐算作服务器私钥的一部分)无需口令即可通过验证&lt;/li&gt;
&lt;li&gt;在线不可伪造：仅能通过访问服务器暴力破解&lt;/li&gt;
&lt;li&gt;Pw-Hiding: 虽然满足，但是不能阻止服务器内部模拟用户&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果再加一个口令是否会更安全？不会，只会更麻烦&lt;/p&gt;
&lt;p&gt;那么mkPBC的方案我认为优势就在于以下几点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;避免服务器内部腐败，擅自模拟用户操作&lt;/li&gt;
&lt;li&gt;强不可伪造和离线不可伪造：服务器妥协和其中一个密钥(k;pw)泄露都无法获取授权&lt;/li&gt;
&lt;li&gt;password可以是便于用户记住的口令，k可以像FIDO方案一样储存，但是对储存方式安全性要求较低&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>Welcome to Twlm&apos;s Blog!</title><link>https://blog.twlmgatito.com/posts/hello/</link><guid isPermaLink="true">https://blog.twlmgatito.com/posts/hello/</guid><pubDate>Mon, 20 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My blog is being rebuilt now.&lt;br /&gt;
More content is on the way.&lt;/p&gt;
</content:encoded></item><item><title>WPF 使用 HLSL + Clip 实现高亮歌词光照效果</title><link>https://blog.twlmgatito.com/posts/wpf-hlsl-text-highlighter/</link><guid isPermaLink="true">https://blog.twlmgatito.com/posts/wpf-hlsl-text-highlighter/</guid><description>本文介绍如何使用HLSL编写一个WPF文本高亮着色器，实现类似Apple Music歌词高亮的光照效果。通过裁剪文本Rectangle并应用ShaderEffect，实现自然的高亮过渡和动画效果。</description><pubDate>Sat, 17 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近在搓一个Lyricify Lite类似物，原本使用渐变画刷实现歌词高亮，但是发现视觉效果与Apple Music相去甚远：单纯使用白色渐变画刷缺乏“高亮”的光照感觉，而Apple Music的歌词高亮则更像是有光线投射在歌词上，形成一种柔和的发光效果。&lt;/p&gt;
&lt;p&gt;受到吕毅大佬的文章&lt;a href=&quot;https://blog.walterlv.com/post/create-a-realistic-sunshine-on-your-desktop-using-wpf.html&quot;&gt;使用 WPF 做一个可以逼真地照亮你桌面的高性能阳光 - walterlv&lt;/a&gt;启发，遂尝试使用HLSL编写一个简单的文本高亮着色器。先来看实装在&lt;a href=&quot;https://github.com/TwilightLemon/LemonLite&quot;&gt;LemonLite&lt;/a&gt;中的效果：&lt;/p&gt;
&lt;p&gt;

&lt;/p&gt;
&lt;p&gt;整体上高亮文本与背景混色自然，渐变过渡由WPF动画驱动，高亮部分就有了光感。&lt;/p&gt;
&lt;p&gt;以下是本文的最小可运行示例：
::github{repo=&quot;TwilightLemon/TextHighlighterTest&quot;}&lt;/p&gt;
&lt;h2&gt;一、实现思路&lt;/h2&gt;
&lt;p&gt;简单说一下踩过的几个坑：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;直接给TextBlock焊上Effect会导致文本像素化，变得模糊而且难以处理文本边界&lt;/li&gt;
&lt;li&gt;使用VisualBrush，内部套一个Rectangle with Effect, 然后用这个VisualBrush作为TextBlock的Foreground，性能极差&lt;/li&gt;
&lt;li&gt;给Rectangle with Effect做一个Clip裁剪出文本形状，性能可以，但是需要复刻TextBlock的排版逻辑&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最终使用了第三种方案，并将其封装成一个用户控件。&lt;/p&gt;
&lt;p&gt;如此一来，HLSL着色器要干的事情就很简单了：取一个高亮过渡位置pos，根据采样像素的X坐标计算光照强度，然后将这个强度与文本颜色做加法混合即可。&lt;/p&gt;
&lt;h2&gt;二、HLSL着色器代码&lt;/h2&gt;
&lt;p&gt;为了适配歌词高亮需求，我又追加了一些特性：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;支持高亮颜色自定义&lt;/li&gt;
&lt;li&gt;支持高亮宽度调整&lt;/li&gt;
&lt;li&gt;支持切换高亮模式（加法混合/线性渐变）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以下是完整的HLSL代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sampler2D input : register(s0);

float HighlightPos : register(c0);
float HighlightWidth : register(c1);
float4 HighlightColor : register(c2);
float UseAdditive : register(c3); // 0 = lerp, 1 = additive
float HighlightIntensity : register(c4);

float4 main(float2 uv : TEXCOORD) : COLOR
{
    float4 color = tex2D(input, uv);
    float d = max(0, uv.x - HighlightPos);
    float glow = saturate(1 - d / HighlightWidth);
    glow = glow * glow;
    float intensity = glow * HighlightColor.a * HighlightIntensity;

    float3 lerpResult = lerp(color.rgb, HighlightColor.rgb, intensity);
    float lerpAlpha = lerp(color.a, 1.0, intensity);
    float3 additiveResult = color.rgb + HighlightColor.rgb * intensity;

    color.rgb = lerp(lerpResult, additiveResult, UseAdditive);
    color.a = lerp(lerpAlpha, color.a, UseAdditive);

    return color;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;着色器输入参数&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输入纹理（文本像素）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HighlightPos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;高亮过渡位置（0-1，从左到右, 当然也可以取负数）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HighlightWidth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;高亮衰减宽度（0-1）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HighlightColor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;高亮颜色（含透明度）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UseAdditive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;混合模式开关（0=线性渐变，1=加法混合）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HighlightIntensity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;高亮强度倍数&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;计算过程&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;采样 → 读取当前像素颜色&lt;br /&gt;
color = tex2D(input, uv)&lt;/li&gt;
&lt;li&gt;计算距离 → 像素X坐标与高亮位置的距离&lt;br /&gt;
d = max(0, uv.x - HighlightPos)&lt;/li&gt;
&lt;li&gt;计算光晕强度 → 根据距离生成平滑衰减&lt;br /&gt;
glow = saturate(1 - d / HighlightWidth)&lt;br /&gt;
glow = glow * glow  // 平方处理，使衰减更陡峭&lt;/li&gt;
&lt;li&gt;最终强度 = 光晕 × 颜色透明度 × 强度倍数&lt;/li&gt;
&lt;li&gt;混合处理 → 根据UseAdditive选择混合方式
&lt;ul&gt;
&lt;li&gt;线性渐变：在原色和高亮色间插值&lt;/li&gt;
&lt;li&gt;加法混合：直接叠加高亮色&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;函数说明&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;saturate(x)&lt;/code&gt;&lt;/strong&gt; - 将值钳制在 [0, 1] 范围内&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;step(edge, x)&lt;/code&gt;&lt;/strong&gt; - 如果 x &amp;lt; edge 返回 0，否则返回 1&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;lerp(a, b, t)&lt;/code&gt;&lt;/strong&gt; - 线性插值，计算 a + (b-a)×t&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;tex2D(sampler, uv)&lt;/code&gt;&lt;/strong&gt; - 从纹理采样指定坐标的像素&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;编译并加载着色器&lt;/h3&gt;
&lt;p&gt;编译HLSL代码生成&lt;code&gt;.ps&lt;/code&gt;文件，然后在WPF中使用&lt;code&gt;PixelShader&lt;/code&gt;类加载：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fxc /T ps_3_0 /E main /Fo TextGlow.ps TextGlow.hlsl
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;封装成Effect类此处不做赘述，只需要严格按照参数顺序传递即可。&lt;/p&gt;
&lt;h2&gt;三、WPF用户控件封装&lt;/h2&gt;
&lt;h3&gt;核心出装&lt;/h3&gt;
&lt;p&gt;用一个 &lt;code&gt;Rectangle&lt;/code&gt; 承载着色器效果，通过&lt;code&gt;FormattedText&lt;/code&gt;生成文本的几何形状作为裁剪路径，这样WPF只会渲染文本区域并且保留清晰的文本边缘。&lt;/p&gt;
&lt;h3&gt;属性继承与元数据覆写&lt;/h3&gt;
&lt;p&gt;为了让高亮控件支持标准的文本属性（字体、大小、粗细等），使用了WPF的元数据覆写机制。在静态构造函数中对 &lt;code&gt;FontFamily&lt;/code&gt;、&lt;code&gt;FontSize&lt;/code&gt;、&lt;code&gt;FontWeight&lt;/code&gt; 等属性进行 &lt;code&gt;OverrideMetadata&lt;/code&gt;，绑定到统一的 &lt;code&gt;OnTextPropertyChanged&lt;/code&gt; 回调。当这些属性变化时，触发文本裁剪的重新计算。类似地，&lt;code&gt;Foreground&lt;/code&gt; 属性也被覆写，当文本颜色改变时直接更新 &lt;code&gt;Rectangle&lt;/code&gt; 的填充颜色。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static HighlightTextBlock()
{
    FontFamilyProperty.OverrideMetadata(typeof(HighlightTextBlock),
        new FrameworkPropertyMetadata(SystemFonts.MessageFontFamily, OnTextPropertyChanged));
    FontSizeProperty.OverrideMetadata(typeof(HighlightTextBlock),
        new FrameworkPropertyMetadata(14.0, OnTextPropertyChanged));
    FontWeightProperty.OverrideMetadata(typeof(HighlightTextBlock),
        new FrameworkPropertyMetadata(FontWeights.Normal, OnTextPropertyChanged));
    ForegroundProperty.OverrideMetadata(typeof(HighlightTextBlock),
        new FrameworkPropertyMetadata(Brushes.Black, OnForegroundChanged));
}

private static void OnForegroundChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    if (d is HighlightTextBlock control)
        control.PART_Rectangle.Fill = e.NewValue as Brush;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样用户就可以像使用普通 &lt;code&gt;TextBlock&lt;/code&gt; 一样使用这个控件，享受XAML的属性绑定和样式系统。&lt;/p&gt;
&lt;h3&gt;文本排版与裁剪&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;UpdateTextClip()&lt;/code&gt; 方法负责：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;创建排版对象&lt;/strong&gt; - 通过 &lt;code&gt;FormattedText&lt;/code&gt; 将文本按照控件的字体、大小、样式参数进行排版，获得与实际渲染完全一致的文本度量&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;var formattedText = new FormattedText(
    Text,
    CultureInfo.CurrentCulture,
    FlowDirection,
    new Typeface(FontFamily, FontStyle, FontWeight, FontStretch),
    FontSize,
    Brushes.Black,
    VisualTreeHelper.GetDpi(this).PixelsPerDip);
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;处理换行与宽度约束&lt;/strong&gt; - 当启用 &lt;code&gt;TextWrapping&lt;/code&gt; 时，从 &lt;code&gt;Width&lt;/code&gt;、&lt;code&gt;MaxWidth&lt;/code&gt; 或 &lt;code&gt;ActualWidth&lt;/code&gt; 中取出约束宽度。只有在需要换行且有约束宽度的情况下，才设置 &lt;code&gt;MaxTextWidth&lt;/code&gt; 让文本自动折行&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;var constraintWidth = !double.IsNaN(Width) &amp;amp;&amp;amp; Width &amp;gt; 0
    ? Width
    : (!double.IsInfinity(MaxWidth) &amp;amp;&amp;amp; MaxWidth &amp;gt; 0 ? MaxWidth : ActualWidth);

if (TextWrapping != TextWrapping.NoWrap &amp;amp;&amp;amp; constraintWidth &amp;gt; 0)
    formattedText.MaxTextWidth = constraintWidth;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;计算容器尺寸&lt;/strong&gt; - NoWrap下容器宽度就是文本宽度，换行模式下则为约束宽度，以确保 &lt;code&gt;Rectangle&lt;/code&gt; 的大小与实际文本范围匹配&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;var containerWidth = TextWrapping == TextWrapping.NoWrap
    ? textWidth
    : (constraintWidth &amp;gt; 0 ? constraintWidth : textWidth);
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;处理文本对齐&lt;/strong&gt; - 根据 &lt;code&gt;TextAlignment&lt;/code&gt; 计算文本相对于容器的起始偏移（用于居中和右对齐），然后生成Rectangle时传入这个偏移量，同时更新 &lt;code&gt;Rectangle&lt;/code&gt; 的水平对齐属性使其与文本对齐方式一致&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;double offsetX = 0;
if (containerWidth &amp;gt; textWidth)
{
    offsetX = TextAlignment switch
    {
        TextAlignment.Center =&amp;gt; (containerWidth - textWidth) / 2,
        TextAlignment.Right =&amp;gt; containerWidth - textWidth,
        _ =&amp;gt; 0
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;生成并应用裁剪&lt;/strong&gt; - 调用 &lt;code&gt;formattedText.BuildGeometry()&lt;/code&gt; 得到精确的文本轮廓Rectangle，设置为 &lt;code&gt;Rectangle.Clip&lt;/code&gt;。这样着色器的输出就被限制在文本像素范围内&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;var geometry = formattedText.BuildGeometry(new Point(offsetX, 0));
PART_Rectangle.Clip = geometry;
PART_Rectangle.Width = containerWidth;
PART_Rectangle.Height = textHeight;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;高亮参数的动画驱动&lt;/h3&gt;
&lt;p&gt;高亮效果的三个关键参数（&lt;code&gt;HighlightPos&lt;/code&gt;、&lt;code&gt;HighlightWidth&lt;/code&gt;、&lt;code&gt;HighlightColor&lt;/code&gt;）都被暴露为依赖属性，在属性变化回调中直接同步到着色器 Effect 对象：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static readonly DependencyProperty HighlightPosProperty =
    DependencyProperty.Register(
        nameof(HighlightPos),
        typeof(double),
        typeof(HighlightTextBlock),
        new PropertyMetadata(0.0, OnHighlightPosChanged));

private static void OnHighlightPosChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    if (d is HighlightTextBlock c)
        c._effect.HighlightPos = (double)e.NewValue;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样就可以在XAML中轻松使用 &lt;code&gt;Storyboard&lt;/code&gt; 和 &lt;code&gt;DoubleAnimation&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Storyboard&amp;gt;
    &amp;lt;DoubleAnimation
        Storyboard.TargetName=&quot;HighlightBlock&quot;
        Storyboard.TargetProperty=&quot;HighlightPos&quot;
        From=&quot;-0.2&quot; To=&quot;1.2&quot; Duration=&quot;0:0:2&quot; RepeatBehavior=&quot;Forever&quot; /&amp;gt;
&amp;lt;/Storyboard&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;驱动 &lt;code&gt;HighlightPos&lt;/code&gt; 的平滑变化，从而实现光线扫过文本的连贯动画效果。&lt;/p&gt;
&lt;h2&gt;四、看看效果&lt;/h2&gt;
&lt;p&gt;
对比Additive叠加和Lerp线性渐变两种模式，可以看到Additive模式下高亮部分更亮更有光感，而Lerp模式尾段偏灰。&lt;/p&gt;
&lt;p&gt;
尝试使用彩色高亮，变成了混色效果。&lt;/p&gt;
&lt;h2&gt;写在最后&lt;/h2&gt;
&lt;p&gt;LemonLite正在龟速开发中，过程中遇到的各种问题和解决方案都会陆续写成博客分享出来，欢迎各位大佬持续关注。&lt;br /&gt;
什么！你问我开头的背景是怎么做的？见上一篇文章：&lt;a href=&quot;/posts/wpf-mica-image-with-major-color-extract/&quot;&gt;WPF 使用GDI+提取图片主色调并生成Mica材质特效背景&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;参考资料：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.walterlv.com/post/create-wpf-pixel-shader-effects-using-shazzam-shader-editor.html&quot;&gt;WPF 像素着色器入门：使用 Shazzam Shader Editor 编写 HLSL 像素着色器代码 - walterlv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.walterlv.com/post/create-a-realistic-sunshine-on-your-desktop-using-wpf.html&quot;&gt;使用 WPF 做一个可以逼真地照亮你桌面的高性能阳光 - walterlv&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>WPF 使用 RenderTransform 实现高性能平滑滚动的 ScrollViewer</title><link>https://blog.twlmgatito.com/posts/wpf-fluent-scrollviewer-v3/</link><guid isPermaLink="true">https://blog.twlmgatito.com/posts/wpf-fluent-scrollviewer-v3/</guid><description>WPF Fluent ScrollViewer 的第三版实现，通过分离视觉层和逻辑层，利用 RenderTransform 实现高性能的平滑滚动。</description><pubDate>Sun, 21 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;在之前的两篇文章中，我们探讨了 WPF 中实现平滑滚动的不同方案：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;/posts/wpf-smooth-scrollviewer&quot;&gt;WPF 如何流畅地滚动ScrollViewer 简单实现下&lt;/a&gt;：基于 &lt;code&gt;DoubleAnimation&lt;/code&gt; 的动画方案。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/wpf-fluent-scrollviewer-with-all-device-supported&quot;&gt;WPF 使用CompositionTarget.Rendering实现平滑流畅滚动的ScrollViewer&lt;/a&gt;：基于 &lt;code&gt;CompositionTarget.Rendering&lt;/code&gt; 的每帧布局更新方案。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;虽然第二版方案解决了触控板和物理惯性的问题，但它引入了一个新的性能瓶颈：&lt;strong&gt;每帧调用 &lt;code&gt;ScrollToVerticalOffset&lt;/code&gt;&lt;/strong&gt;。这会导致 WPF 在每一帧都进行布局计算，在高负载场景下会直接卡死整个UI线程，造成掉帧或其他UI组件无响应。&lt;/p&gt;
&lt;p&gt;为了解决这个问题，我进行了第三版（v3）设计，核心思路是：&lt;strong&gt;视觉层与逻辑层分离&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;三种方案对比&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方案&lt;/th&gt;
&lt;th&gt;实现方式&lt;/th&gt;
&lt;th&gt;优点&lt;/th&gt;
&lt;th&gt;缺点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;v1 (动画版)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DoubleAnimation&lt;/code&gt; 驱动 &lt;code&gt;VerticalOffset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;实现简单，代码量少&lt;/td&gt;
&lt;td&gt;无法保留惯性速度（动画打断）；触控板体验差；不支持触摸/笔。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;v2 (布局驱动)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Rendering&lt;/code&gt; 事件每帧调用 &lt;code&gt;ScrollToVerticalOffset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;物理模型更真实；支持多种输入设备&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;性能差&lt;/strong&gt;：每帧触发 Layout Pass，高负载下掉帧严重。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;v3 (视觉分离)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RenderTransform&lt;/code&gt; 驱动视觉，低频同步逻辑位置&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;高性能&lt;/strong&gt;：视觉满帧运行，逻辑低频更新；物理模型完善。&lt;/td&gt;
&lt;td&gt;实现相对复杂，需要处理坐标系转换和帧同步。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;p&gt;接下来，我们详细介绍 v3 版本的设计与实现原理。&lt;/p&gt;
&lt;h2&gt;一、视觉与逻辑分离&lt;/h2&gt;
&lt;p&gt;v3 的核心在于将“用户看到的滚动”（视觉层）和“控件实际的滚动”（逻辑层）分离。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;视觉层&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;TranslateTransform&lt;/code&gt; 对 &lt;code&gt;Content&lt;/code&gt; 进行位移。&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;CompositionTarget.Rendering&lt;/code&gt; 中以屏幕刷新率（如 60Hz 或 144Hz）更新 &lt;code&gt;Transform.Y&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;因为 &lt;code&gt;RenderTransform&lt;/code&gt; 只影响渲染而不触发布局（Measure/Arrange），所以性能极高，完全由 GPU 加速。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;逻辑层&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;维护实际的 &lt;code&gt;ScrollViewer.VerticalOffset&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;降频更新&lt;/strong&gt;：不再每帧调用 &lt;code&gt;ScrollToVerticalOffset&lt;/code&gt;，而是以较低的频率（如 24Hz）同步逻辑位置。&lt;/li&gt;
&lt;li&gt;这保证了滚动条的位置更新和虚拟化加载新内容，同时避免了频繁的布局计算。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;渲染循环逻辑&lt;/h3&gt;
&lt;p&gt;只需遵守一条坐标系变换规则：&lt;strong&gt;逻辑位置 = 视觉位置 + 视觉偏差&lt;/strong&gt;。&lt;br /&gt;
当用户滚动时，视觉层将以“插帧”方式在逻辑层低帧率更新之间平滑过渡。&lt;/p&gt;
&lt;p&gt;在每一帧的渲染回调中：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;计算物理模型的当前位置 &lt;code&gt;_currentVisualOffset&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;计算视觉偏差 &lt;code&gt;_visualDelta = _currentVisualOffset - _logicalOffset&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;应用 &lt;code&gt;_transform.Y = -_visualDelta&lt;/code&gt;，实现视觉上的平滑移动，不会触发布局重置。&lt;/li&gt;
&lt;li&gt;累加时间，如果超过 &lt;code&gt;ScrollBarUpdateInterval&lt;/code&gt; (1/24s)，则调用 &lt;code&gt;ScrollToVerticalOffset&lt;/code&gt; 同步逻辑位置，触发布局更新，从而允许滚动条同步和虚拟化等功能生效。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;二、物理模型设计&lt;/h2&gt;
&lt;p&gt;v3 版本的物理模型沿用了 v2 的设计，但做了一些改进以提升滚动体验。以下介绍完整的物理模型。&lt;/p&gt;
&lt;h3&gt;2.1 缓动模型&lt;/h3&gt;
&lt;p&gt;适用于鼠标滚轮。该模型包含两个核心部分：&lt;strong&gt;动态速度因子&lt;/strong&gt;（决定滚多快）和&lt;strong&gt;物理衰减&lt;/strong&gt;（决定滚多久）。&lt;/p&gt;
&lt;h4&gt;动态速度因子 (Dynamic Velocity Factor)&lt;/h4&gt;
&lt;p&gt;在 v2 版本中，我们发现简单的线性速度叠加无法平衡缓慢滚动和快速滚动的体验。因此，v3 引入了一个基于时间间隔的动态速度因子。当用户快速连续滚动时，速度因子会呈指数级增长，从而产生更大的加速度。&lt;/p&gt;
&lt;p&gt;$$ v_{factor} = (V_{max} - V_{min}) \cdot e^{-\frac{\Delta t}{20}} + V_{min} $$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$V_{max}$：最大速度倍率，固定为 2.5。&lt;/li&gt;
&lt;li&gt;$V_{min}$：最小速度倍率 (&lt;code&gt;MinVelocityFactor&lt;/code&gt;)，默认为 1.2。&lt;/li&gt;
&lt;li&gt;$\Delta t$：两次滚动事件的时间间隔 (ms)。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这意味着：如果你慢慢滚动，每次滚动的距离约为原始值的 1.2 倍；如果你疯狂拨动滚轮，这个倍率会迅速逼近 2.5 倍，与真实的物理滚动手感更接近。&lt;/p&gt;
&lt;h4&gt;物理衰减&lt;/h4&gt;
&lt;p&gt;模拟物理摩擦力，使滚动速度随时间自然衰减。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;速度衰减&lt;/strong&gt;：$v_{new} = v_{old} \cdot f^{t_{factor}}$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;位置更新&lt;/strong&gt;：$x_{new} = x_{old} + v_{new} \cdot \frac{t_{factor}}{24}$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$f$：速率衰减系数，默认为 0.92。数值越小，停得越快。&lt;/li&gt;
&lt;li&gt;$t_{factor}$：时间标准化因子，$\frac{dt}{TargetFrameTime}$ (基准帧率为 144Hz)。&lt;/li&gt;
&lt;li&gt;常数 24 是一个经验值，用于调整速度到位移的映射比例。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2 精确模型&lt;/h3&gt;
&lt;p&gt;适用于触控板。触控板本身提供了高精度的 &lt;code&gt;Delta&lt;/code&gt; 值，我们不需要模拟惯性（系统已处理），只需要平滑地过渡到目标位置，避免画面撕裂或抖动。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;插值计算&lt;/strong&gt;：$x_{new} = x_{old} + (x_{target} - x_{old}) \cdot (1 - (1 - l)^{t_{factor}})$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$l$：插值系数 (&lt;code&gt;LerpFactor&lt;/code&gt;)，默认为 0.5。数值越大，跟随越紧密。&lt;/li&gt;
&lt;li&gt;$t_{factor}$：时间标准化因子，$\frac{dt}{TargetFrameTime}$。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三、快速开始&lt;/h2&gt;
&lt;p&gt;在项目中引入FluentWpfCore包，然后使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Window xmlns:fluent=&quot;clr-namespace:FluentWpf.Controls;assembly=FluentWpfCore&quot; 
        ...&amp;gt;

&amp;lt;fluent:SmoothScrollViewer&amp;gt;
    &amp;lt;!--可选 自定义模型及其参数--&amp;gt;
    &amp;lt;fluent:SmoothScrollViewer.Physics&amp;gt;
        &amp;lt;fluent:DefaultScrollPhysics MinVelocityFactor=&quot;1.2&quot; /&amp;gt;
    &amp;lt;/fluent:SmoothScrollViewer.Physics&amp;gt;
    ...
&amp;lt;/fluent:SmoothScrollViewer&amp;gt;

&amp;lt;/Window&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;IsEnableSmoothScrolling&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;启用或禁用平滑滚动动画（实际上会控制所有SmoothScrolling相关功能）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PreferredScrollOrientation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Orientation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Vertical&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;首选滚动方向：&lt;code&gt;Vertical&lt;/code&gt; 或 &lt;code&gt;Horizontal&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AllowTogglePreferredScrollOrientationByShiftKey&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;允许通过按住 Shift 键切换滚动方向&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Physics&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IScrollPhysics&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DefaultScrollPhysics&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;控制滚动动画行为的物理模型&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;默认模型(&lt;code&gt;DefaultScrollPhysics&lt;/code&gt;)可选参数：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MinVelocityFactor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;double&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1.2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;鼠标滚轮的最小速度倍率&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Friction&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;double&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.92&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;鼠标滚轮的速度衰减系数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LerpFactor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;double&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;触控板滚动的插值系数&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;四、了解更多&lt;/h2&gt;
&lt;h3&gt;1) 为什么要“视觉满帧、逻辑低频”&lt;/h3&gt;
&lt;p&gt;在 WPF 里，&lt;code&gt;ScrollToVerticalOffset/ScrollToHorizontalOffset&lt;/code&gt; 不是一个“只改数值”的轻量操作。它往往会驱动：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;滚动条位置与 &lt;code&gt;ScrollChanged&lt;/code&gt; 事件&lt;/li&gt;
&lt;li&gt;布局与渲染链路（尤其是内容复杂时）&lt;/li&gt;
&lt;li&gt;虚拟化容器的生成/回收（例如 &lt;code&gt;VirtualizingStackPanel&lt;/code&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;v2的实现把它放到 &lt;code&gt;CompositionTarget.Rendering&lt;/code&gt; 的每一帧里调用，意味着UI线程必须在每帧都完成布局计算，这在内容复杂或CPU负载高时会直接卡死UI线程，导致掉帧或其他UI组件无响应。&lt;/p&gt;
&lt;p&gt;v3 的分层策略是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;视觉层&lt;/strong&gt;：用 &lt;code&gt;TranslateTransform&lt;/code&gt; 做位移补偿，只影响渲染，不触发布局。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;逻辑层&lt;/strong&gt;：用 &lt;code&gt;ScrollTo*Offset&lt;/code&gt; 推进真实偏移，但频率降低到 24Hz。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;相当于把“高频的连续运动”交给 GPU（RenderTransform），把“低频但必要的状态推进”交给布局系统（ScrollTo）。或者理解为：&lt;strong&gt;视觉层做“动画”，逻辑层做“状态更新”&lt;/strong&gt;，以低帧率推进布局计算，然后由视觉层平滑过渡，显著提升性能。&lt;/p&gt;
&lt;h3&gt;2) 关键状态：视觉差值（Visual Delta）&lt;/h3&gt;
&lt;p&gt;在 v3 里，始终存在两个 offset：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;逻辑 offset&lt;/strong&gt;：&lt;code&gt;ScrollViewer&lt;/code&gt; 真正的 &lt;code&gt;VerticalOffset/HorizontalOffset&lt;/code&gt;，决定滚动条与虚拟化。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;视觉 offset&lt;/strong&gt;：物理模型在每帧计算出的“应该看到的位置”。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两者的差值就是视觉补偿量：&lt;/p&gt;
&lt;p&gt;$$\Delta = offset_{visual} - offset_{logical}$$&lt;/p&gt;
&lt;p&gt;视觉层每帧做的事情非常纯粹：把这个差值通过 Transform 反向抵消掉，让用户“看到”的内容位置跟随视觉 offset。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;垂直滚动：&lt;code&gt;Transform.Y = -$\Delta$&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;水平滚动：&lt;code&gt;Transform.X = -$\Delta$&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样带来两个好处：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;逻辑层什么时候同步（调用 &lt;code&gt;ScrollTo*Offset&lt;/code&gt;）可以自由选择频率，不会影响视觉连续性。&lt;/li&gt;
&lt;li&gt;当逻辑层因为外部原因突变（拖动滚动条、代码调用 &lt;code&gt;ScrollTo...&lt;/code&gt;、键盘导航）时，只要立刻重算差值，视觉层就仍然能保持连续。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3) 为什么要用真实 &lt;code&gt;dt&lt;/code&gt;（而不是假设固定帧率）&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;CompositionTarget.Rendering&lt;/code&gt; 的触发并不严格等间隔：后台负载、窗口被遮挡、显示器刷新率、系统节能策略都会让帧间隔波动。&lt;/p&gt;
&lt;p&gt;因此实现中用 &lt;code&gt;Stopwatch.GetTimestamp()&lt;/code&gt; 计算真实 &lt;code&gt;dt&lt;/code&gt;，并把 &lt;code&gt;dt&lt;/code&gt; 传给物理模型。这意味着：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;低帧率时不会“走慢动作”或突然“加速冲刺”&lt;/li&gt;
&lt;li&gt;高刷屏（120/144Hz）不会因为更高帧数而滚得更远&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;配合 &lt;code&gt;DefaultScrollPhysics&lt;/code&gt; 中的 &lt;code&gt;timeFactor = dt / TargetFrameTime&lt;/code&gt;，滚动手感可以在不同帧率下保持一致。&lt;/p&gt;
&lt;h3&gt;4) 关于逻辑同步频率&lt;/h3&gt;
&lt;p&gt;实现把逻辑同步频率设为 24Hz（&lt;code&gt;ScrollBarUpdateInterval = 1/24s&lt;/code&gt;），这是一个折中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;频率更高：滚动条更“实时”，虚拟化更及时，但布局压力上升。&lt;/li&gt;
&lt;li&gt;频率更低：性能更好，但滚动条会有视觉滞后，虚拟化加载可能会出现空白频闪。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;一个可能的缓解思路是让虚拟化容器提前加载，会增加一点内存开销，但能减少空白频闪。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;一般来说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;内容很重（大量图片、复杂控件、阴影/模糊多）：可以把同步频率调低一点。&lt;/li&gt;
&lt;li&gt;列表虚拟化强依赖“及时生成下一屏”（例如聊天列表/文件列表）：可以适当提高，但要观察 CPU。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5) 注意 ScrollChanged 状态变更&lt;/h3&gt;
&lt;p&gt;只靠渲染循环还不够，因为用户可以通过滚动条拖动来改变 offset。实现里在 &lt;code&gt;OnScrollChanged&lt;/code&gt; 中做了两件重要的事情：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;更新逻辑 offset（垂直/水平各自维护）。&lt;/li&gt;
&lt;li&gt;如果当前正在平滑滚动，并且变化来自“当前激活方向”，就立刻更新 Transform，让画面位置保持连续。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;6) 横向滚动与 Shift 切换&lt;/h3&gt;
&lt;p&gt;v3 支持横向与纵向两种滚动方向，并且提供了按住 Shift 切换滚动方向的特性。&lt;/p&gt;
&lt;h3&gt;7) 为什么滚动时要临时关闭 HitTest&lt;/h3&gt;
&lt;p&gt;渲染循环开始时把 &lt;code&gt;IsHitTestVisible&lt;/code&gt; 设为 &lt;code&gt;false&lt;/code&gt;，结束时恢复。&lt;/p&gt;
&lt;p&gt;高速滚动时，鼠标在大量元素上扫过会触发频繁的命中测试和状态变更（Hover、ToolTip、触发器）。
关闭命中测试能够显著降低这些开销，提升滚动性能。&lt;/p&gt;
&lt;p&gt;当然，它也意味着滚动过程中无法点击内容。&lt;/p&gt;
&lt;h3&gt;8) 如何写你自己的物理模型&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;IScrollPhysics&lt;/code&gt; 的接口设计刻意简单：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;OnScroll(...)&lt;/code&gt;：只负责接收一次输入意图（delta + 是否精确 + 边界 + 时间间隔）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Update(...)&lt;/code&gt;：每帧推进到新位置（帧率无关）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;IsStable&lt;/code&gt;：告诉外部何时可以退出渲染循环。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;写在最后&lt;/h2&gt;
&lt;p&gt;::github{repo=&quot;TwilightLemon/FluentWpfCore&quot;}
相关组件均开源在 FluentWpfCore 仓库，欢迎 star 和 PR！仓库保持活跃更新。&lt;/p&gt;
&lt;p&gt;感谢阅读，文章如有不妥之处，请各位大佬不吝指正！&lt;/p&gt;
</content:encoded></item><item><title>WPF 为ContextMenu使用Fluent风格的亚克力材质特效</title><link>https://blog.twlmgatito.com/posts/wpf-fluent-contextmenu-with-arcrylic/</link><guid isPermaLink="true">https://blog.twlmgatito.com/posts/wpf-fluent-contextmenu-with-arcrylic/</guid><description>Fluent风格的亚克力材质ContextMenu，以及较为完整的MenuItem样式</description><pubDate>Wed, 19 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;书接上回，我们的Fluent WPF的版图已经完成了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fluent Window: &lt;a href=&quot;/posts/window-material-in-wpf/&quot;&gt;WPF 模拟UWP原生窗口样式——亚克力|云母材质、自定义标题栏样式、原生DWM动画 （附我封装好的类）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Fluent Popup &amp;amp; ToolTip: &lt;a href=&quot;/posts/wpf-use-windowmaterial-in-popup-and-tooltip/&quot;&gt;WPF中为Popup和ToolTip使用WindowMaterial特效 win10/win11&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Fluent ScrollViewer: &lt;a href=&quot;/posts/wpf-fluent-scrollviewer-with-all-device-supported/&quot;&gt;WPF 使用CompositionTarget.Rendering实现平滑流畅滚动的ScrollViewer，支持滚轮、触控板、触摸屏和笔&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;先来看看效果图(win11)：&lt;br /&gt;
有以下xaml代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; &amp;lt;TextBlock.ContextMenu&amp;gt;
     &amp;lt;ContextMenu&amp;gt;
         &amp;lt;MenuItem Header=&quot;Menu Item 1&quot; Icon=&quot;Cd&quot; InputGestureText=&quot;aa?&quot; /&amp;gt;
         &amp;lt;MenuItem Header=&quot;Menu Item 2&quot; &amp;gt;
             &amp;lt;MenuItem Header=&quot;Child Item 1&quot; Icon=&quot;Ab&quot; /&amp;gt;
             &amp;lt;MenuItem Header=&quot;Child Item 2&quot; Icon=&quot;Ad&quot; /&amp;gt;
             &amp;lt;MenuItem Header=&quot;Child Item 3&quot; IsCheckable=&quot;True&quot; IsChecked=&quot;True&quot;/&amp;gt;
         &amp;lt;/MenuItem&amp;gt;
         &amp;lt;MenuItem Header=&quot;Menu Item 3&quot; Icon=&quot;cd&quot; /&amp;gt;
     &amp;lt;/ContextMenu&amp;gt;
 &amp;lt;/TextBlock.ContextMenu&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
&lt;br /&gt;
(由于我的win10虚拟机坏了，暂时没有测试)&lt;/p&gt;
&lt;p&gt;前面的工作已经解决了让任意窗口支持Acrylic材质的问题，本文重点介绍ContentMenu和MenuItem的样式适配和实现。&lt;/p&gt;
&lt;p&gt;本文的Demo：&lt;br /&gt;
::github{repo=&quot;TwilightLemon/WindowEffectTest&quot;}&lt;/p&gt;
&lt;h2&gt;一、为什么需要一个新的ContextMenu和MenuItem样式&lt;/h2&gt;
&lt;p&gt;如果你直接给ContextMenu应用WindowMaterial特效，可能会出现以下丑陋的效果：&lt;br /&gt;

或者：&lt;br /&gt;

原因在于古老的ContextMenu和MenuItem样式并不能通过简单修改Background实现我们想要的布局和交互效果。&lt;/p&gt;
&lt;h2&gt;二、ContextMenu与MenuItem的结构&lt;/h2&gt;
&lt;h3&gt;1. ContextMenu 的结构&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ContextMenu&lt;/code&gt;直观上看是一个&lt;code&gt;Popup&lt;/code&gt;，但它的控件模板并不包含&lt;code&gt;Popup&lt;/code&gt;，而是直接指定内部元素（包含一个&lt;code&gt;ScrollViewer&lt;/code&gt;和&lt;code&gt;StackPanel&lt;/code&gt;）。因此不能从控件模板中替换Popup为自定义的&lt;code&gt;FluentPopup&lt;/code&gt;，需要使用其他手段让其内部Popup也支持Acrylic材质（见下文）。&lt;/p&gt;
&lt;h3&gt;2. MenuItem 的四种形态&lt;/h3&gt;
&lt;p&gt;在示例代码仓库中展示了较为完整的Menu相关的结构：
&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MenuItem&lt;/code&gt;根据其在菜单树中的位置，通过&lt;code&gt;Role&lt;/code&gt;属性分为四种形态。我们在&lt;code&gt;Style.Triggers&lt;/code&gt;中分别为它们指定了不同的模板：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TopLevelHeader&lt;/strong&gt;: 顶级菜单项，且包含子菜单&lt;code&gt;Popup&lt;/code&gt;（例如菜单栏上的&quot;File&quot;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TopLevelItem&lt;/strong&gt;: 顶级菜单项，不含子菜单（例如菜单栏上的&quot;Help&quot;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SubmenuHeader&lt;/strong&gt;: 子菜单项，且包含下一级子菜单&lt;code&gt;Popup&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SubmenuItem&lt;/strong&gt;: 子菜单项，不含子菜单（叶子节点）。其模板主要处理图标、文字、快捷键的布局。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三、重写Menu相关控件模板和样式&lt;/h2&gt;
&lt;h3&gt;1. ContextMenu 样式&lt;/h3&gt;
&lt;p&gt;ContextMenu的样式主要参考了.NET 9自带的Fluent样式，并作了一些调整以适配Acrylic材质。主要目的是覆盖原始模板的Icon部分白框和分割线：

因为我们的WindowMaterial已经为窗口自动附加上圆角、阴影和亚克力材质的DWM效果，所以我们只需要将ContextMenu的背景设置为透明，并移除不必要的边框和分割线即可。&lt;br /&gt;
此外，还添加了弹出动画（是在Popup内部做的，并非对window，效果可能不会很理想）。&lt;/p&gt;
&lt;h3&gt;2. MenuItem 样式&lt;/h3&gt;
&lt;p&gt;MenuItem的样式主要处理了图标、文字、快捷键的布局，并根据不同的角色（TopLevelHeader、TopLevelItem、SubmenuHeader、SubmenuItem）应用不同的模板。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TopLevelHeader: 只包含Icon和Header，以及弹出的Popup，只需要替换为自定义的FluentPopup即可。&lt;/li&gt;
&lt;li&gt;TopLevelItem: 只包含Icon和Header，无Popup。&lt;/li&gt;
&lt;li&gt;SubmenuHeader: 包含Icon、Header和Chevron图标(这里就是一个展开的箭头图标，但是官方叫做雪佛龙..?)，以及弹出的Popup，同样替换为FluentPopup。&lt;/li&gt;
&lt;li&gt;SubmenuItem: 叶子节点，包含Icon、Header和InputGestureText。 其模板主要处理图标、文字、快捷键的布局。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;菜单项主要分为三个部分：Icon图标、Header文字和最右侧的提示文字或展开箭头图标。只需要保持三个部分的布局对其即可。如果IsCheckable为True，则Icon部分被自定义图标占据(√, 当IsChecked为True时)。&lt;br /&gt;
&lt;/p&gt;
&lt;p&gt;以下是完整的资源字典，包含了完整的注释。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;ResourceDictionary
    xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
    xmlns:x=&quot;http://schemas.microsoft.com/winfx/2006/xaml&quot;
    xmlns:local=&quot;clr-namespace:WindowEffectTest&quot;
    xmlns:sys=&quot;clr-namespace:System;assembly=mscorlib&quot;&amp;gt;

    &amp;lt;!--  菜单边框内边距  --&amp;gt;
    &amp;lt;Thickness x:Key=&quot;MenuBorderPadding&quot;&amp;gt;0,3,0,3&amp;lt;/Thickness&amp;gt;

    &amp;lt;!--  菜单项外边距  --&amp;gt;
    &amp;lt;Thickness x:Key=&quot;MenuItemMargin&quot;&amp;gt;4,1&amp;lt;/Thickness&amp;gt;

    &amp;lt;!--  菜单项内容内边距  --&amp;gt;
    &amp;lt;Thickness x:Key=&quot;MenuItemContentPadding&quot;&amp;gt;10,6&amp;lt;/Thickness&amp;gt;

    &amp;lt;!--  顶级菜单项外边距  --&amp;gt;
    &amp;lt;Thickness x:Key=&quot;TopLevelItemMargin&quot;&amp;gt;4&amp;lt;/Thickness&amp;gt;

    &amp;lt;!--  顶级菜单项内容边距  --&amp;gt;
    &amp;lt;Thickness x:Key=&quot;TopLevelContentMargin&quot;&amp;gt;10&amp;lt;/Thickness&amp;gt;

    &amp;lt;!--  菜单圆角半径  --&amp;gt;
    &amp;lt;CornerRadius x:Key=&quot;MenuCornerRadius&quot;&amp;gt;4&amp;lt;/CornerRadius&amp;gt;

    &amp;lt;!--  顶级菜单圆角半径  --&amp;gt;
    &amp;lt;CornerRadius x:Key=&quot;TopLevelCornerRadius&quot;&amp;gt;6&amp;lt;/CornerRadius&amp;gt;

    &amp;lt;!--  菜单动画持续时间  --&amp;gt;
    &amp;lt;Duration x:Key=&quot;MenuAnimationDuration&quot;&amp;gt;0:0:0.167&amp;lt;/Duration&amp;gt;

    &amp;lt;!--  复选标记图标路径数据  --&amp;gt;
    &amp;lt;PathGeometry x:Key=&quot;CheckGraph&quot;&amp;gt;
        M392.533333 806.4L85.333333 503.466667l59.733334-59.733334 247.466666 247.466667L866.133333 213.333333l59.733334 59.733334L392.533333 806.4z
    &amp;lt;/PathGeometry&amp;gt;

    &amp;lt;!--  前进箭头图标路径数据（用于子菜单指示器）  --&amp;gt;
    &amp;lt;PathGeometry x:Key=&quot;ForwardGraph&quot;&amp;gt;
        M283.648 174.081l57.225-59.008 399.479 396.929-399.476 396.924-57.228-59.004 335.872-337.92z
    &amp;lt;/PathGeometry&amp;gt;

    &amp;lt;!--  菜单项ScrollViewer样式  --&amp;gt;
    &amp;lt;Style
        x:Key=&quot;MenuItemScrollViewerStyle&quot;
        BasedOn=&quot;{StaticResource {x:Type ScrollViewer}}&quot;
        TargetType=&quot;{x:Type ScrollViewer}&quot;&amp;gt;
        &amp;lt;Setter Property=&quot;HorizontalScrollBarVisibility&quot; Value=&quot;Disabled&quot; /&amp;gt;
        &amp;lt;Setter Property=&quot;VerticalScrollBarVisibility&quot; Value=&quot;Auto&quot; /&amp;gt;
    &amp;lt;/Style&amp;gt;

    &amp;lt;!--  默认集合焦点视觉样式  得到键盘焦点时显示  --&amp;gt;
    &amp;lt;Style x:Key=&quot;DefaultCollectionFocusVisualStyle&quot;&amp;gt;
        &amp;lt;Setter Property=&quot;Control.Template&quot;&amp;gt;
            &amp;lt;Setter.Value&amp;gt;
                &amp;lt;ControlTemplate&amp;gt;
                    &amp;lt;Rectangle
                        Margin=&quot;4,0&quot;
                        RadiusX=&quot;4&quot;
                        RadiusY=&quot;4&quot;
                        SnapsToDevicePixels=&quot;True&quot;
                        Stroke=&quot;{DynamicResource AccentColor}&quot;
                        StrokeThickness=&quot;2&quot; /&amp;gt;
                &amp;lt;/ControlTemplate&amp;gt;
            &amp;lt;/Setter.Value&amp;gt;
        &amp;lt;/Setter&amp;gt;
    &amp;lt;/Style&amp;gt;

    &amp;lt;!--  默认上下文菜单样式  --&amp;gt;
    &amp;lt;Style x:Key=&quot;DefaultContextMenuStyle&quot; TargetType=&quot;{x:Type ContextMenu}&quot;&amp;gt;
        &amp;lt;Setter Property=&quot;MinWidth&quot; Value=&quot;140&quot; /&amp;gt;
        &amp;lt;Setter Property=&quot;Padding&quot; Value=&quot;0&quot; /&amp;gt;
        &amp;lt;Setter Property=&quot;Margin&quot; Value=&quot;0&quot; /&amp;gt;
        &amp;lt;Setter Property=&quot;HasDropShadow&quot; Value=&quot;False&quot; /&amp;gt;
        &amp;lt;Setter Property=&quot;Grid.IsSharedSizeScope&quot; Value=&quot;True&quot; /&amp;gt;
        &amp;lt;Setter Property=&quot;Popup.PopupAnimation&quot; Value=&quot;None&quot; /&amp;gt;
        &amp;lt;Setter Property=&quot;SnapsToDevicePixels&quot; Value=&quot;True&quot; /&amp;gt;
        &amp;lt;Setter Property=&quot;OverridesDefaultStyle&quot; Value=&quot;True&quot; /&amp;gt;
        &amp;lt;Setter Property=&quot;Template&quot;&amp;gt;
            &amp;lt;Setter.Value&amp;gt;
                &amp;lt;ControlTemplate TargetType=&quot;{x:Type ContextMenu}&quot;&amp;gt;
                    &amp;lt;Border
                        x:Name=&quot;Border&quot;
                        Padding=&quot;{StaticResource MenuBorderPadding}&quot;
                        Background=&quot;{TemplateBinding Background}&quot;
                        BorderBrush=&quot;{TemplateBinding BorderBrush}&quot;
                        BorderThickness=&quot;{TemplateBinding BorderThickness}&quot;&amp;gt;
                        &amp;lt;!--  用于动画的转换变换  --&amp;gt;
                        &amp;lt;Border.RenderTransform&amp;gt;
                            &amp;lt;TranslateTransform /&amp;gt;
                        &amp;lt;/Border.RenderTransform&amp;gt;
                        &amp;lt;ScrollViewer CanContentScroll=&quot;True&quot; Style=&quot;{StaticResource MenuItemScrollViewerStyle}&quot;&amp;gt;
                            &amp;lt;!--  菜单项容器  --&amp;gt;
                            &amp;lt;StackPanel
                                ClipToBounds=&quot;True&quot;
                                IsItemsHost=&quot;True&quot;
                                KeyboardNavigation.DirectionalNavigation=&quot;Cycle&quot;
                                Orientation=&quot;Vertical&quot; /&amp;gt;
                        &amp;lt;/ScrollViewer&amp;gt;
                    &amp;lt;/Border&amp;gt;
                    &amp;lt;ControlTemplate.Triggers&amp;gt;
                        &amp;lt;!--  菜单打开时的动画效果  --&amp;gt;
                        &amp;lt;Trigger Property=&quot;IsOpen&quot; Value=&quot;True&quot;&amp;gt;
                            &amp;lt;Trigger.EnterActions&amp;gt;
                                &amp;lt;BeginStoryboard&amp;gt;
                                    &amp;lt;Storyboard&amp;gt;
                                        &amp;lt;!--  Y轴平移动画：从-45向下滑入到0  --&amp;gt;
                                        &amp;lt;DoubleAnimation
                                            Storyboard.TargetName=&quot;Border&quot;
                                            Storyboard.TargetProperty=&quot;(Border.RenderTransform).(TranslateTransform.Y)&quot;
                                            From=&quot;-45&quot;
                                            To=&quot;0&quot;
                                            Duration=&quot;{StaticResource MenuAnimationDuration}&quot;&amp;gt;
                                            &amp;lt;DoubleAnimation.EasingFunction&amp;gt;
                                                &amp;lt;CircleEase EasingMode=&quot;EaseOut&quot; /&amp;gt;
                                            &amp;lt;/DoubleAnimation.EasingFunction&amp;gt;
                                        &amp;lt;/DoubleAnimation&amp;gt;
                                    &amp;lt;/Storyboard&amp;gt;
                                &amp;lt;/BeginStoryboard&amp;gt;
                            &amp;lt;/Trigger.EnterActions&amp;gt;
                        &amp;lt;/Trigger&amp;gt;
                    &amp;lt;/ControlTemplate.Triggers&amp;gt;
                &amp;lt;/ControlTemplate&amp;gt;
            &amp;lt;/Setter.Value&amp;gt;
        &amp;lt;/Setter&amp;gt;
    &amp;lt;/Style&amp;gt;

    &amp;lt;!--  顶级菜单项头部模板（带子菜单）  --&amp;gt;
    &amp;lt;ControlTemplate x:Key=&quot;{x:Static MenuItem.TopLevelHeaderTemplateKey}&quot; TargetType=&quot;{x:Type MenuItem}&quot;&amp;gt;
        &amp;lt;Border
            x:Name=&quot;Border&quot;
            Margin=&quot;{StaticResource TopLevelItemMargin}&quot;
            Background=&quot;{TemplateBinding Background}&quot;
            BorderBrush=&quot;{TemplateBinding BorderBrush}&quot;
            BorderThickness=&quot;{TemplateBinding BorderThickness}&quot;
            CornerRadius=&quot;{StaticResource TopLevelCornerRadius}&quot;&amp;gt;
            &amp;lt;Grid&amp;gt;
                &amp;lt;Grid.RowDefinitions&amp;gt;
                    &amp;lt;RowDefinition Height=&quot;*&quot; /&amp;gt;
                    &amp;lt;RowDefinition Height=&quot;Auto&quot; /&amp;gt;
                &amp;lt;/Grid.RowDefinitions&amp;gt;

                &amp;lt;!--  菜单项内容区域  --&amp;gt;
                &amp;lt;Grid Margin=&quot;{StaticResource TopLevelContentMargin}&quot;&amp;gt;
                    &amp;lt;Grid.ColumnDefinitions&amp;gt;
                        &amp;lt;ColumnDefinition Width=&quot;Auto&quot; /&amp;gt;
                        &amp;lt;ColumnDefinition Width=&quot;*&quot; /&amp;gt;
                    &amp;lt;/Grid.ColumnDefinitions&amp;gt;

                    &amp;lt;!--  菜单项图标  --&amp;gt;
                    &amp;lt;ContentPresenter
                        x:Name=&quot;Icon&quot;
                        Grid.Column=&quot;0&quot;
                        Margin=&quot;0,0,6,0&quot;
                        VerticalAlignment=&quot;Center&quot;
                        Content=&quot;{TemplateBinding Icon}&quot;
                        SnapsToDevicePixels=&quot;{TemplateBinding SnapsToDevicePixels}&quot; /&amp;gt;

                    &amp;lt;!--  菜单项标题  --&amp;gt;
                    &amp;lt;ContentPresenter
                        x:Name=&quot;HeaderPresenter&quot;
                        Grid.Column=&quot;1&quot;
                        Margin=&quot;{TemplateBinding Padding}&quot;
                        VerticalAlignment=&quot;Center&quot;
                        ContentSource=&quot;Header&quot;
                        RecognizesAccessKey=&quot;True&quot;
                        SnapsToDevicePixels=&quot;{TemplateBinding SnapsToDevicePixels}&quot;
                        TextElement.Foreground=&quot;{TemplateBinding Foreground}&quot; /&amp;gt;
                &amp;lt;/Grid&amp;gt;

                &amp;lt;!--  子菜单弹出窗口（使用自定义FluentPopup）  --&amp;gt;
                &amp;lt;local:FluentPopup
                    x:Name=&quot;PART_Popup&quot;
                    Grid.Row=&quot;1&quot;
                    Grid.Column=&quot;0&quot;
                    Focusable=&quot;False&quot;
                    HorizontalOffset=&quot;-12&quot;
                    IsOpen=&quot;{TemplateBinding IsSubmenuOpen}&quot;
                    Placement=&quot;Bottom&quot;
                    PlacementTarget=&quot;{Binding ElementName=Border}&quot;
                    PopupAnimation=&quot;None&quot;
                    VerticalOffset=&quot;1&quot;&amp;gt;
                    &amp;lt;Grid
                        x:Name=&quot;SubmenuBorder&quot;
                        Background=&quot;{DynamicResource PopupWindowBackground}&quot;
                        SnapsToDevicePixels=&quot;True&quot;&amp;gt;
                        &amp;lt;!--  子菜单动画变换  --&amp;gt;
                        &amp;lt;Grid.RenderTransform&amp;gt;
                            &amp;lt;TranslateTransform /&amp;gt;
                        &amp;lt;/Grid.RenderTransform&amp;gt;
                        &amp;lt;ScrollViewer
                            Padding=&quot;{StaticResource MenuBorderPadding}&quot;
                            CanContentScroll=&quot;True&quot;
                            Style=&quot;{StaticResource MenuItemScrollViewerStyle}&quot;&amp;gt;
                            &amp;lt;Grid&amp;gt;
                                &amp;lt;!--  子菜单项呈现器  --&amp;gt;
                                &amp;lt;ItemsPresenter
                                    x:Name=&quot;ItemsPresenter&quot;
                                    Grid.IsSharedSizeScope=&quot;True&quot;
                                    KeyboardNavigation.DirectionalNavigation=&quot;Cycle&quot;
                                    KeyboardNavigation.TabNavigation=&quot;Cycle&quot;
                                    SnapsToDevicePixels=&quot;{TemplateBinding SnapsToDevicePixels}&quot; /&amp;gt;
                            &amp;lt;/Grid&amp;gt;
                        &amp;lt;/ScrollViewer&amp;gt;
                    &amp;lt;/Grid&amp;gt;
                &amp;lt;/local:FluentPopup&amp;gt;
            &amp;lt;/Grid&amp;gt;
        &amp;lt;/Border&amp;gt;
        &amp;lt;ControlTemplate.Triggers&amp;gt;
            &amp;lt;!--  无图标时隐藏图标区域  --&amp;gt;
            &amp;lt;Trigger Property=&quot;Icon&quot; Value=&quot;{x:Null}&quot;&amp;gt;
                &amp;lt;Setter TargetName=&quot;Icon&quot; Property=&quot;Visibility&quot; Value=&quot;Collapsed&quot; /&amp;gt;
            &amp;lt;/Trigger&amp;gt;
            &amp;lt;!--  无标题时隐藏标题并移除图标边距  --&amp;gt;
            &amp;lt;Trigger Property=&quot;Header&quot; Value=&quot;{x:Null}&quot;&amp;gt;
                &amp;lt;Setter TargetName=&quot;Icon&quot; Property=&quot;Margin&quot; Value=&quot;0&quot; /&amp;gt;
                &amp;lt;Setter TargetName=&quot;HeaderPresenter&quot; Property=&quot;Visibility&quot; Value=&quot;Collapsed&quot; /&amp;gt;
            &amp;lt;/Trigger&amp;gt;
            &amp;lt;!--  鼠标悬停高亮效果  --&amp;gt;
            &amp;lt;Trigger Property=&quot;IsHighlighted&quot; Value=&quot;True&quot;&amp;gt;
                &amp;lt;Setter TargetName=&quot;Border&quot; Property=&quot;Background&quot; Value=&quot;{DynamicResource MaskColor}&quot; /&amp;gt;
            &amp;lt;/Trigger&amp;gt;
            &amp;lt;!--  子菜单打开时的动画  --&amp;gt;
            &amp;lt;Trigger Property=&quot;IsSubmenuOpen&quot; Value=&quot;True&quot;&amp;gt;
                &amp;lt;Trigger.EnterActions&amp;gt;
                    &amp;lt;BeginStoryboard&amp;gt;
                        &amp;lt;Storyboard&amp;gt;
                            &amp;lt;DoubleAnimation
                                Storyboard.TargetName=&quot;SubmenuBorder&quot;
                                Storyboard.TargetProperty=&quot;(Border.RenderTransform).(TranslateTransform.Y)&quot;
                                From=&quot;-45&quot;
                                To=&quot;0&quot;
                                Duration=&quot;{StaticResource MenuAnimationDuration}&quot;&amp;gt;
                                &amp;lt;DoubleAnimation.EasingFunction&amp;gt;
                                    &amp;lt;CircleEase EasingMode=&quot;EaseOut&quot; /&amp;gt;
                                &amp;lt;/DoubleAnimation.EasingFunction&amp;gt;
                            &amp;lt;/DoubleAnimation&amp;gt;
                        &amp;lt;/Storyboard&amp;gt;
                    &amp;lt;/BeginStoryboard&amp;gt;
                &amp;lt;/Trigger.EnterActions&amp;gt;
            &amp;lt;/Trigger&amp;gt;
        &amp;lt;/ControlTemplate.Triggers&amp;gt;
    &amp;lt;/ControlTemplate&amp;gt;

    &amp;lt;!--  顶级菜单项模板（无子菜单）  --&amp;gt;
    &amp;lt;ControlTemplate x:Key=&quot;{x:Static MenuItem.TopLevelItemTemplateKey}&quot; TargetType=&quot;{x:Type MenuItem}&quot;&amp;gt;
        &amp;lt;Border
            x:Name=&quot;Border&quot;
            Margin=&quot;{StaticResource TopLevelItemMargin}&quot;
            Background=&quot;{TemplateBinding Background}&quot;
            BorderBrush=&quot;{TemplateBinding BorderBrush}&quot;
            BorderThickness=&quot;{TemplateBinding BorderThickness}&quot;
            CornerRadius=&quot;{StaticResource TopLevelCornerRadius}&quot;&amp;gt;
            &amp;lt;Grid Margin=&quot;{StaticResource TopLevelContentMargin}&quot;&amp;gt;
                &amp;lt;Grid.ColumnDefinitions&amp;gt;
                    &amp;lt;ColumnDefinition Width=&quot;Auto&quot; /&amp;gt;
                    &amp;lt;ColumnDefinition Width=&quot;*&quot; /&amp;gt;
                &amp;lt;/Grid.ColumnDefinitions&amp;gt;

                &amp;lt;!--  图标区域  --&amp;gt;
                &amp;lt;ContentPresenter
                    x:Name=&quot;Icon&quot;
                    Grid.Column=&quot;0&quot;
                    Margin=&quot;0,0,6,0&quot;
                    VerticalAlignment=&quot;Center&quot;
                    Content=&quot;{TemplateBinding Icon}&quot;
                    SnapsToDevicePixels=&quot;{TemplateBinding SnapsToDevicePixels}&quot; /&amp;gt;

                &amp;lt;!--  标题区域  --&amp;gt;
                &amp;lt;ContentPresenter
                    x:Name=&quot;HeaderPresenter&quot;
                    Grid.Column=&quot;1&quot;
                    Margin=&quot;{TemplateBinding Padding}&quot;
                    VerticalAlignment=&quot;Center&quot;
                    ContentSource=&quot;Header&quot;
                    RecognizesAccessKey=&quot;True&quot;
                    SnapsToDevicePixels=&quot;{TemplateBinding SnapsToDevicePixels}&quot;
                    TextElement.Foreground=&quot;{TemplateBinding Foreground}&quot; /&amp;gt;
            &amp;lt;/Grid&amp;gt;
        &amp;lt;/Border&amp;gt;
        &amp;lt;ControlTemplate.Triggers&amp;gt;
            &amp;lt;!--  鼠标悬停效果  --&amp;gt;
            &amp;lt;Trigger Property=&quot;IsHighlighted&quot; Value=&quot;True&quot;&amp;gt;
                &amp;lt;Setter TargetName=&quot;Border&quot; Property=&quot;Background&quot; Value=&quot;{DynamicResource MaskColor}&quot; /&amp;gt;
            &amp;lt;/Trigger&amp;gt;
            &amp;lt;Trigger Property=&quot;Icon&quot; Value=&quot;{x:Null}&quot;&amp;gt;
                &amp;lt;Setter TargetName=&quot;Icon&quot; Property=&quot;Visibility&quot; Value=&quot;Collapsed&quot; /&amp;gt;
            &amp;lt;/Trigger&amp;gt;
            &amp;lt;Trigger Property=&quot;Header&quot; Value=&quot;{x:Null}&quot;&amp;gt;
                &amp;lt;Setter TargetName=&quot;Icon&quot; Property=&quot;Margin&quot; Value=&quot;0&quot; /&amp;gt;
                &amp;lt;Setter TargetName=&quot;HeaderPresenter&quot; Property=&quot;Visibility&quot; Value=&quot;Collapsed&quot; /&amp;gt;
            &amp;lt;/Trigger&amp;gt;
        &amp;lt;/ControlTemplate.Triggers&amp;gt;
    &amp;lt;/ControlTemplate&amp;gt;

    &amp;lt;!--  子菜单项模板（无子级）  --&amp;gt;
    &amp;lt;ControlTemplate x:Key=&quot;{x:Static MenuItem.SubmenuItemTemplateKey}&quot; TargetType=&quot;{x:Type MenuItem}&quot;&amp;gt;
        &amp;lt;Border
            x:Name=&quot;Border&quot;
            Margin=&quot;{StaticResource MenuItemMargin}&quot;
            Background=&quot;{TemplateBinding Background}&quot;
            BorderBrush=&quot;{TemplateBinding BorderBrush}&quot;
            BorderThickness=&quot;{TemplateBinding BorderThickness}&quot;
            CornerRadius=&quot;{StaticResource MenuCornerRadius}&quot;&amp;gt;
            &amp;lt;Grid Margin=&quot;{StaticResource MenuItemContentPadding}&quot;&amp;gt;
                &amp;lt;Grid.ColumnDefinitions&amp;gt;
                    &amp;lt;!--  图标/复选框列，使用共享大小组确保对齐  --&amp;gt;
                    &amp;lt;ColumnDefinition Width=&quot;Auto&quot; SharedSizeGroup=&quot;MenuItemIconCol&quot; /&amp;gt;
                    &amp;lt;!--  标题内容列  --&amp;gt;
                    &amp;lt;ColumnDefinition Width=&quot;*&quot; /&amp;gt;
                    &amp;lt;!--  快捷键提示列，使用共享大小组确保对齐  --&amp;gt;
                    &amp;lt;ColumnDefinition Width=&quot;Auto&quot; SharedSizeGroup=&quot;MenuItemRightPartCol&quot; /&amp;gt;
                &amp;lt;/Grid.ColumnDefinitions&amp;gt;

                &amp;lt;!--  复选框图标容器  --&amp;gt;
                &amp;lt;Border
                    x:Name=&quot;CheckBoxIconBorder&quot;
                    Grid.Column=&quot;0&quot;
                    VerticalAlignment=&quot;Center&quot;
                    Visibility=&quot;Collapsed&quot;&amp;gt;
                    &amp;lt;Path
                        x:Name=&quot;CheckBoxIcon&quot;
                        Width=&quot;10&quot;
                        Height=&quot;10&quot;
                        HorizontalAlignment=&quot;Left&quot;
                        VerticalAlignment=&quot;Center&quot;
                        Fill=&quot;{TemplateBinding Foreground}&quot;
                        Stretch=&quot;Uniform&quot; /&amp;gt;
                &amp;lt;/Border&amp;gt;

                &amp;lt;!--  自定义图标  --&amp;gt;
                &amp;lt;ContentPresenter
                    x:Name=&quot;Icon&quot;
                    Grid.Column=&quot;0&quot;
                    Margin=&quot;0,0,6,0&quot;
                    VerticalAlignment=&quot;Center&quot;
                    Content=&quot;{TemplateBinding Icon}&quot;
                    SnapsToDevicePixels=&quot;{TemplateBinding SnapsToDevicePixels}&quot; /&amp;gt;

                &amp;lt;!--  菜单项标题内容  --&amp;gt;
                &amp;lt;ContentPresenter
                    Grid.Column=&quot;1&quot;
                    Margin=&quot;{TemplateBinding Padding}&quot;
                    VerticalAlignment=&quot;{TemplateBinding VerticalContentAlignment}&quot;
                    ContentSource=&quot;Header&quot;
                    RecognizesAccessKey=&quot;True&quot;
                    SnapsToDevicePixels=&quot;{TemplateBinding SnapsToDevicePixels}&quot;
                    TextElement.Foreground=&quot;{TemplateBinding Foreground}&quot; /&amp;gt;

                &amp;lt;!--  快捷键提示文本  --&amp;gt;
                &amp;lt;TextBlock
                    x:Name=&quot;InputGestureText&quot;
                    Grid.Column=&quot;2&quot;
                    Margin=&quot;25,0,0,0&quot;
                    VerticalAlignment=&quot;{TemplateBinding VerticalContentAlignment}&quot;
                    DockPanel.Dock=&quot;Right&quot;
                    FontSize=&quot;11&quot;
                    Opacity=&quot;0.67&quot;
                    Text=&quot;{TemplateBinding InputGestureText}&quot; /&amp;gt;
            &amp;lt;/Grid&amp;gt;
        &amp;lt;/Border&amp;gt;
        &amp;lt;ControlTemplate.Triggers&amp;gt;
            &amp;lt;!--  高亮状态（鼠标悬停）  --&amp;gt;
            &amp;lt;Trigger Property=&quot;IsHighlighted&quot; Value=&quot;True&quot;&amp;gt;
                &amp;lt;Setter TargetName=&quot;Border&quot; Property=&quot;Background&quot; Value=&quot;{DynamicResource MaskColor}&quot; /&amp;gt;
            &amp;lt;/Trigger&amp;gt;
            &amp;lt;!--  无自定义图标时隐藏图标区域  --&amp;gt;
            &amp;lt;Trigger Property=&quot;Icon&quot; Value=&quot;{x:Null}&quot;&amp;gt;
                &amp;lt;Setter TargetName=&quot;Icon&quot; Property=&quot;Visibility&quot; Value=&quot;Collapsed&quot; /&amp;gt;
            &amp;lt;/Trigger&amp;gt;
            &amp;lt;!--  可复选时显示复选框图标容器  --&amp;gt;
            &amp;lt;Trigger Property=&quot;IsCheckable&quot; Value=&quot;True&quot;&amp;gt;
                &amp;lt;Setter TargetName=&quot;CheckBoxIconBorder&quot; Property=&quot;Visibility&quot; Value=&quot;Visible&quot; /&amp;gt;
            &amp;lt;/Trigger&amp;gt;
            &amp;lt;!--  已选中时显示复选标记  --&amp;gt;
            &amp;lt;Trigger Property=&quot;IsChecked&quot; Value=&quot;True&quot;&amp;gt;
                &amp;lt;Setter TargetName=&quot;CheckBoxIcon&quot; Property=&quot;Data&quot; Value=&quot;{StaticResource CheckGraph}&quot; /&amp;gt;
            &amp;lt;/Trigger&amp;gt;
            &amp;lt;!--  无快捷键时隐藏快捷键提示  --&amp;gt;
            &amp;lt;Trigger Property=&quot;InputGestureText&quot; Value=&quot;&quot;&amp;gt;
                &amp;lt;Setter TargetName=&quot;InputGestureText&quot; Property=&quot;Visibility&quot; Value=&quot;Collapsed&quot; /&amp;gt;
            &amp;lt;/Trigger&amp;gt;
        &amp;lt;/ControlTemplate.Triggers&amp;gt;
    &amp;lt;/ControlTemplate&amp;gt;

    &amp;lt;!--  子菜单头部模板（带下级子菜单）  --&amp;gt;
    &amp;lt;ControlTemplate x:Key=&quot;{x:Static MenuItem.SubmenuHeaderTemplateKey}&quot; TargetType=&quot;{x:Type MenuItem}&quot;&amp;gt;
        &amp;lt;Grid&amp;gt;
            &amp;lt;Grid.RowDefinitions&amp;gt;
                &amp;lt;RowDefinition Height=&quot;*&quot; /&amp;gt;
                &amp;lt;RowDefinition Height=&quot;Auto&quot; /&amp;gt;
            &amp;lt;/Grid.RowDefinitions&amp;gt;

            &amp;lt;!--  菜单项外观  --&amp;gt;
            &amp;lt;Border
                x:Name=&quot;Border&quot;
                Grid.Row=&quot;1&quot;
                Height=&quot;{TemplateBinding Height}&quot;
                Margin=&quot;{StaticResource MenuItemMargin}&quot;
                Background=&quot;Transparent&quot;
                CornerRadius=&quot;{StaticResource MenuCornerRadius}&quot;&amp;gt;
                &amp;lt;Grid x:Name=&quot;MenuItemContent&quot; Margin=&quot;{StaticResource MenuItemContentPadding}&quot;&amp;gt;
                    &amp;lt;Grid.ColumnDefinitions&amp;gt;
                        &amp;lt;ColumnDefinition Width=&quot;Auto&quot; SharedSizeGroup=&quot;MenuItemIconCol&quot; /&amp;gt;
                        &amp;lt;ColumnDefinition Width=&quot;*&quot; /&amp;gt;
                        &amp;lt;ColumnDefinition Width=&quot;Auto&quot; SharedSizeGroup=&quot;MenuItemRightPartCol&quot; /&amp;gt;
                    &amp;lt;/Grid.ColumnDefinitions&amp;gt;

                    &amp;lt;!--  图标  --&amp;gt;
                    &amp;lt;ContentPresenter
                        x:Name=&quot;Icon&quot;
                        Grid.Column=&quot;0&quot;
                        Margin=&quot;0,0,6,0&quot;
                        VerticalAlignment=&quot;Center&quot;
                        Content=&quot;{TemplateBinding Icon}&quot;
                        SnapsToDevicePixels=&quot;{TemplateBinding SnapsToDevicePixels}&quot; /&amp;gt;

                    &amp;lt;!--  标题  --&amp;gt;
                    &amp;lt;ContentPresenter
                        x:Name=&quot;HeaderHost&quot;
                        Grid.Column=&quot;1&quot;
                        Margin=&quot;{TemplateBinding Padding}&quot;
                        VerticalAlignment=&quot;{TemplateBinding VerticalContentAlignment}&quot;
                        ContentSource=&quot;Header&quot;
                        RecognizesAccessKey=&quot;True&quot;
                        SnapsToDevicePixels=&quot;{TemplateBinding SnapsToDevicePixels}&quot; /&amp;gt;

                    &amp;lt;!--  右箭头指示器（表示有子菜单）  --&amp;gt;
                    &amp;lt;Path
                        Grid.Column=&quot;2&quot;
                        Width=&quot;10&quot;
                        Height=&quot;10&quot;
                        Margin=&quot;0,0,4,0&quot;
                        HorizontalAlignment=&quot;Right&quot;
                        VerticalAlignment=&quot;Center&quot;
                        Data=&quot;{StaticResource ForwardGraph}&quot;
                        Fill=&quot;{TemplateBinding Foreground}&quot;
                        Opacity=&quot;0.67&quot;
                        Stretch=&quot;Uniform&quot; /&amp;gt;
                &amp;lt;/Grid&amp;gt;
            &amp;lt;/Border&amp;gt;

            &amp;lt;!--  子菜单弹出窗口（向右展开）  --&amp;gt;
            &amp;lt;local:FluentPopup
                x:Name=&quot;PART_Popup&quot;
                Grid.Row=&quot;1&quot;
                Focusable=&quot;False&quot;
                IsOpen=&quot;{TemplateBinding IsSubmenuOpen}&quot;
                Placement=&quot;Right&quot;
                PlacementTarget=&quot;{Binding ElementName=MenuItemContent}&quot;
                PopupAnimation=&quot;None&quot;&amp;gt;
                &amp;lt;Grid x:Name=&quot;PopupRoot&quot; Background=&quot;{DynamicResource PopupWindowBackground}&quot;&amp;gt;
                    &amp;lt;!--  子菜单动画变换  --&amp;gt;
                    &amp;lt;Grid.RenderTransform&amp;gt;
                        &amp;lt;TranslateTransform /&amp;gt;
                    &amp;lt;/Grid.RenderTransform&amp;gt;
                    &amp;lt;ScrollViewer
                        Padding=&quot;{StaticResource MenuBorderPadding}&quot;
                        CanContentScroll=&quot;True&quot;
                        Style=&quot;{StaticResource MenuItemScrollViewerStyle}&quot;&amp;gt;
                        &amp;lt;!--  子菜单项容器  --&amp;gt;
                        &amp;lt;ItemsPresenter
                            x:Name=&quot;ItemsPresenter&quot;
                            Grid.IsSharedSizeScope=&quot;True&quot;
                            KeyboardNavigation.DirectionalNavigation=&quot;Cycle&quot;
                            KeyboardNavigation.TabNavigation=&quot;Cycle&quot;
                            SnapsToDevicePixels=&quot;{TemplateBinding SnapsToDevicePixels}&quot; /&amp;gt;
                    &amp;lt;/ScrollViewer&amp;gt;
                &amp;lt;/Grid&amp;gt;
            &amp;lt;/local:FluentPopup&amp;gt;
        &amp;lt;/Grid&amp;gt;
        &amp;lt;ControlTemplate.Triggers&amp;gt;
            &amp;lt;!--  无图标时优化布局  --&amp;gt;
            &amp;lt;Trigger Property=&quot;Icon&quot; Value=&quot;{x:Null}&quot;&amp;gt;
                &amp;lt;Setter TargetName=&quot;Icon&quot; Property=&quot;Visibility&quot; Value=&quot;Collapsed&quot; /&amp;gt;
                &amp;lt;Setter TargetName=&quot;Icon&quot; Property=&quot;Margin&quot; Value=&quot;0&quot; /&amp;gt;
            &amp;lt;/Trigger&amp;gt;
            &amp;lt;!--  高亮效果  --&amp;gt;
            &amp;lt;Trigger Property=&quot;IsHighlighted&quot; Value=&quot;true&quot;&amp;gt;
                &amp;lt;Setter TargetName=&quot;Border&quot; Property=&quot;Background&quot; Value=&quot;{DynamicResource MaskColor}&quot; /&amp;gt;
            &amp;lt;/Trigger&amp;gt;
            &amp;lt;!--  子菜单打开动画  --&amp;gt;
            &amp;lt;Trigger Property=&quot;IsSubmenuOpen&quot; Value=&quot;True&quot;&amp;gt;
                &amp;lt;Trigger.EnterActions&amp;gt;
                    &amp;lt;BeginStoryboard&amp;gt;
                        &amp;lt;Storyboard&amp;gt;
                            &amp;lt;!--  Y轴平移动画：从-45向下滑入到0  --&amp;gt;
                            &amp;lt;DoubleAnimation
                                Storyboard.TargetName=&quot;PopupRoot&quot;
                                Storyboard.TargetProperty=&quot;(Grid.RenderTransform).(TranslateTransform.Y)&quot;
                                From=&quot;-45&quot;
                                To=&quot;0&quot;
                                Duration=&quot;{StaticResource MenuAnimationDuration}&quot;&amp;gt;
                                &amp;lt;DoubleAnimation.EasingFunction&amp;gt;
                                    &amp;lt;CircleEase EasingMode=&quot;EaseOut&quot; /&amp;gt;
                                &amp;lt;/DoubleAnimation.EasingFunction&amp;gt;
                            &amp;lt;/DoubleAnimation&amp;gt;
                        &amp;lt;/Storyboard&amp;gt;
                    &amp;lt;/BeginStoryboard&amp;gt;
                &amp;lt;/Trigger.EnterActions&amp;gt;
            &amp;lt;/Trigger&amp;gt;
        &amp;lt;/ControlTemplate.Triggers&amp;gt;
    &amp;lt;/ControlTemplate&amp;gt;

    &amp;lt;!--  默认菜单项样式  --&amp;gt;
    &amp;lt;Style x:Key=&quot;DefaultMenuItemStyle&quot; TargetType=&quot;{x:Type MenuItem}&quot;&amp;gt;
        &amp;lt;Setter Property=&quot;FocusVisualStyle&quot; Value=&quot;{DynamicResource DefaultCollectionFocusVisualStyle}&quot; /&amp;gt;
        &amp;lt;Setter Property=&quot;KeyboardNavigation.IsTabStop&quot; Value=&quot;True&quot; /&amp;gt;
        &amp;lt;Setter Property=&quot;Background&quot; Value=&quot;Transparent&quot; /&amp;gt;
        &amp;lt;Setter Property=&quot;BorderBrush&quot; Value=&quot;Transparent&quot; /&amp;gt;
        &amp;lt;Setter Property=&quot;BorderThickness&quot; Value=&quot;1&quot; /&amp;gt;
        &amp;lt;Setter Property=&quot;Focusable&quot; Value=&quot;True&quot; /&amp;gt;
        &amp;lt;Setter Property=&quot;OverridesDefaultStyle&quot; Value=&quot;True&quot; /&amp;gt;
        &amp;lt;Style.Triggers&amp;gt;
            &amp;lt;!--  根据菜单项角色应用不同模板  --&amp;gt;

            &amp;lt;!--  顶级菜单项（带子菜单）  --&amp;gt;
            &amp;lt;Trigger Property=&quot;Role&quot; Value=&quot;TopLevelHeader&quot;&amp;gt;
                &amp;lt;Setter Property=&quot;Template&quot; Value=&quot;{StaticResource {x:Static MenuItem.TopLevelHeaderTemplateKey}}&quot; /&amp;gt;
                &amp;lt;Setter Property=&quot;Grid.IsSharedSizeScope&quot; Value=&quot;True&quot; /&amp;gt;
                &amp;lt;Setter Property=&quot;Height&quot; Value=&quot;{x:Static sys:Double.NaN}&quot; /&amp;gt;
            &amp;lt;/Trigger&amp;gt;

            &amp;lt;!--  顶级菜单项（无子菜单）  --&amp;gt;
            &amp;lt;Trigger Property=&quot;Role&quot; Value=&quot;TopLevelItem&quot;&amp;gt;
                &amp;lt;Setter Property=&quot;Template&quot; Value=&quot;{StaticResource {x:Static MenuItem.TopLevelItemTemplateKey}}&quot; /&amp;gt;
                &amp;lt;Setter Property=&quot;Height&quot; Value=&quot;{x:Static sys:Double.NaN}&quot; /&amp;gt;
            &amp;lt;/Trigger&amp;gt;

            &amp;lt;!--  子菜单项（带子菜单）  --&amp;gt;
            &amp;lt;Trigger Property=&quot;Role&quot; Value=&quot;SubmenuHeader&quot;&amp;gt;
                &amp;lt;Setter Property=&quot;Template&quot; Value=&quot;{StaticResource {x:Static MenuItem.SubmenuHeaderTemplateKey}}&quot; /&amp;gt;
            &amp;lt;/Trigger&amp;gt;

            &amp;lt;!--  子菜单项（无子菜单）  --&amp;gt;
            &amp;lt;Trigger Property=&quot;Role&quot; Value=&quot;SubmenuItem&quot;&amp;gt;
                &amp;lt;Setter Property=&quot;Template&quot; Value=&quot;{StaticResource {x:Static MenuItem.SubmenuItemTemplateKey}}&quot; /&amp;gt;
            &amp;lt;/Trigger&amp;gt;
        &amp;lt;/Style.Triggers&amp;gt;
    &amp;lt;/Style&amp;gt;

&amp;lt;/ResourceDictionary&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、应用模板和样式到全局&lt;/h2&gt;
&lt;p&gt;将上面的资源字典合并到应用程序资源中，然后为ContextMenu和MenuItem指定默认样式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; &amp;lt;Style BasedOn=&quot;{StaticResource DefaultContextMenuStyle}&quot; TargetType=&quot;{x:Type ContextMenu}&quot;&amp;gt;
     &amp;lt;Style.Setters&amp;gt;
         &amp;lt;Setter Property=&quot;local:FluentTooltip.UseFluentStyle&quot; Value=&quot;True&quot; /&amp;gt;
         &amp;lt;Setter Property=&quot;Background&quot; Value=&quot;{DynamicResource PopupWindowBackground}&quot; /&amp;gt;
         &amp;lt;Setter Property=&quot;Foreground&quot; Value=&quot;{DynamicResource ForeColor}&quot; /&amp;gt;
     &amp;lt;/Style.Setters&amp;gt;
 &amp;lt;/Style&amp;gt;
 &amp;lt;Style BasedOn=&quot;{StaticResource DefaultMenuItemStyle}&quot; TargetType=&quot;MenuItem&quot;&amp;gt;
     &amp;lt;Setter Property=&quot;Height&quot; Value=&quot;36&quot; /&amp;gt;
     &amp;lt;Setter Property=&quot;Foreground&quot; Value=&quot;{DynamicResource ForeColor}&quot; /&amp;gt;
     &amp;lt;Setter Property=&quot;VerticalContentAlignment&quot; Value=&quot;Center&quot; /&amp;gt;
 &amp;lt;/Style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意，ContextMenu需要使用之前文章中的&lt;code&gt;FluentTooltip.UseFluentStyle&lt;/code&gt;来实现亚克力材质特效。其内部原理都是反射获取popup的hwnd句柄，然后附加WindowMaterial特效。&lt;/p&gt;
&lt;h2&gt;参考连接&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/desktop/wpf/controls/contextmenu-styles-and-templates&quot;&gt;ContextMenu Styles and Templates WPF | Microsoft Learn&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/desktop/wpf/controls/menu-styles-and-templates&quot;&gt;Menu Styles and Templates WPF | Microsoft Learn&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/dotnet/wpf/blob/main/src/Microsoft.DotNet.Wpf/src/Themes/PresentationFramework.Fluent/Themes/Fluent.xaml&quot;&gt;PresentationFramework.Fluent/Themes/Fluent.xaml | GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>WPF 使用CompositionTarget.Rendering实现平滑流畅滚动的ScrollViewer，支持滚轮、触控板、触摸屏和笔</title><link>https://blog.twlmgatito.com/posts/wpf-fluent-scrollviewer-with-all-device-supported/</link><guid isPermaLink="true">https://blog.twlmgatito.com/posts/wpf-fluent-scrollviewer-with-all-device-supported/</guid><pubDate>Sat, 08 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;之前的文章中用WPF自带的动画库实现了一个简陋的平滑滚动ScrollViewer，它在只使用鼠标滚轮的情况下表现良好，但仍然有明显的设计缺陷和不足：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;没有实现真正的动画衔接，只是单纯结束掉上一个动画，而不是继承其滚动速率；&lt;/li&gt;
&lt;li&gt;使用触控板的体验极差&lt;/li&gt;
&lt;li&gt;对触控屏和笔设备无效&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;为了解决以上问题，本文提出一种新的方案来实现平滑滚动ScrollViewer。该方案在&lt;code&gt;OnMouseWheel&lt;/code&gt;、&lt;code&gt;OnManipulationDelta&lt;/code&gt;和&lt;code&gt;OnManipulationCompleted&lt;/code&gt;中直接处理(禁用)了系统的滚动效果，使用&lt;code&gt;CompositionTarget.Rendering&lt;/code&gt;事件来驱动滚动动画。并针对滚轮方式和触控“跟手”分别进行优化，使用&lt;code&gt;缓动滚动模型&lt;/code&gt;和&lt;code&gt;精确滚动模型&lt;/code&gt;来实现平滑滚动。笔的支持得益于&lt;code&gt;EleCho.WpfSuite&lt;/code&gt;库提供的&lt;code&gt;StylusTouchDevice&lt;/code&gt;模拟，将笔输入映射为触摸输入。&lt;/p&gt;
&lt;p&gt;为了最直观和最简单地解决问题，我们将应用场景设置为垂直滚动，水平滚动可以通过类似的方式实现。
在github中查看最小可运行代码：&lt;br /&gt;
::github{repo=&quot;TwilightLemon/FluentScrollViewer&quot;}&lt;/p&gt;
&lt;h1&gt;一、一些先验事实和设计思路&lt;/h1&gt;
&lt;h2&gt;1.1 OnMouseWheel的触发逻辑&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;OnMouseWheel(MouseWheelEventArgs e)&lt;/code&gt;事件由WPF触发，&lt;code&gt;e.Delta&lt;/code&gt;指示鼠标单次滚动的偏移值，通常为120或-120，这个值可以通过&lt;code&gt;Mouse.MouseWheelDeltaForOneLine&lt;/code&gt;获得。这一逻辑在传统鼠标滚轮上顺理成章，但是在精准滚动设备（如触控板）上，滚动偏移量变得非常小，事件在高频率低偏移地触发，导致基于动画触发的滚动体验不佳。&lt;br /&gt;
测试发现，在以下两种场景中，OnMouseWheel事件具有特定的行为：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;设备&lt;/th&gt;
&lt;th&gt;缓慢滚动&lt;/th&gt;
&lt;th&gt;快速滚动&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;鼠标滚轮&lt;/td&gt;
&lt;td&gt;单个触发、一次一个事件&lt;/td&gt;
&lt;td&gt;可能多个合并触发，e.Delta 是滚动值的倍数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;触控板&lt;/td&gt;
&lt;td&gt;持续滚动，间隔触发，e.Delta 值很小&lt;/td&gt;
&lt;td&gt;Delta 快速增长，最后变为很小的值&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;因为触控板、触摸屏等精准滚动的使用场景，设备与人交互，意味着其数据本身就遵循物理规律。但是滚动的速率和距离被离散地传递给WPF，导致了滚动的生硬和不自然。&lt;br /&gt;
那么有没有一种思路，我们只需先接收这些滚动数据，然后在每一帧中根据这些数据来计算滚动位置？相当于把离散的滚动数据重新平滑化。&lt;/p&gt;
&lt;h2&gt;1.2 CompositionTarget.Rendering&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;CompositionTarget.Rendering&lt;/code&gt;是WPF渲染管线的一个事件，它在每一帧渲染之前触发。我们可以利用这个事件来实现自定义的滚动逻辑：先收集滚动参数，然后在OnRender事件中计算实际偏移值，并应用到ScrollViewer上。&lt;/p&gt;
&lt;h2&gt;1.3 两种场景、两种模型&lt;/h2&gt;
&lt;p&gt;我们将滚动分为两种场景：滚轮和触控，分别对应缓动滚动模型和精确滚动模型。&lt;/p&gt;
&lt;h3&gt;1.3.1 缓动滚动模型&lt;/h3&gt;
&lt;p&gt;类似于鞭挞陀螺使其旋转，每打一次都会给陀螺附加新的加速度，然后在接下来的时间中由于摩擦的存在而缓慢减速。我们基于这个思路来实现简易的缓动滚动模型：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先定义几个常量：衰减系数&lt;code&gt;f=0.92&lt;/code&gt;、叠加速率力度系数&lt;code&gt;n=2.0&lt;/code&gt;、目标帧时间&lt;code&gt;frameTime=1.0/144&lt;/code&gt;以及行进常量&lt;code&gt;p=24&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;每次&lt;code&gt;OnMouseWheel&lt;/code&gt;事件触发时，叠加速率：&lt;code&gt;v += e.Delta * n&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;在&lt;code&gt;CompositionTarget.Rendering&lt;/code&gt;事件中：
&lt;ul&gt;
&lt;li&gt;计算刷新间隔时间&lt;code&gt;Δt&lt;/code&gt;，以按照间隔时间计算滚动增量&lt;/li&gt;
&lt;li&gt;&lt;code&gt;t= Δt / frameTime&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;v *= f^t&lt;/code&gt;，模拟摩擦力的影响（确保在目标帧时间内衰减幅度一致）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;offset += v *(t/p)&lt;/code&gt;，计算滚动增量&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;将新的位置应用到ScrollViewer上&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;调整以上参数会带来相应的变化：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;衰减系数f越小，滚动越快停下来&lt;/li&gt;
&lt;li&gt;叠加速率力度系数n越大，每次滚动的速度越快&lt;/li&gt;
&lt;li&gt;行进常量p越大，滚动越慢&lt;/li&gt;
&lt;li&gt;通常你无需调整frameTime，它只是用来标准化滚动速度的&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.3.2 精确滚动模型&lt;/h3&gt;
&lt;p&gt;对于一个指定的滚动距离，我们希望能够精确地滚动到目标位置，而不是依赖于速率和衰减。模型只需要对离散距离补帧即可。具体而言，定义一个插值系数l，指示接近目标位置的速率，则&lt;code&gt;offset=_targetOffset - _currentOffset) *l&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;2025/11/08 更新&lt;/h3&gt;
&lt;p&gt;因为&lt;code&gt;CompositionTarget.Rendering&lt;/code&gt;事件的触发频率并不固定，可能会因为系统负载等原因而变化较大，因此在计算滚动增量时需要考虑实际的时间增量。&lt;/p&gt;
&lt;h1&gt;二、实现&lt;/h1&gt;
&lt;p&gt;现在我们已经有思路了：先捕获&lt;code&gt;OnMouseWheel&lt;/code&gt;等事件-&amp;gt;判断使用哪个模型-&amp;gt;挂载&lt;code&gt;OnRender&lt;/code&gt;事件-&amp;gt;在每一帧中计算新的滚动位置-&amp;gt;应用到ScrollViewer上。以下实现通过继承&lt;code&gt;ScrollViewer&lt;/code&gt;创建新的控件来实现。&lt;/p&gt;
&lt;h2&gt;2.1 先从鼠标滚轮与触控板开始&lt;/h2&gt;
&lt;p&gt;从&lt;code&gt;OnMouseWheel&lt;/code&gt;中收集数据并判断模型:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; protected override void OnMouseWheel(MouseWheelEventArgs e)
 {
     e.Handled = true;

     //触摸板使用精确滚动模型
     _isAccuracyControl = IsTouchpadScroll(e);

     if (_isAccuracyControl)
         {
            _targetVelocity = 0; // 防止下一次触发缓动模型时继承没有消除的速度，造成滚动异常
            _targetOffset = Math.Clamp(_currentOffset - e.Delta, 0, ScrollableHeight);
        }
     else
         _targetVelocity += -e.Delta * VelocityFactor;// 鼠标滚动，叠加速度（惯性滚动）

     if (!_isRenderingHooked)
     {
         CompositionTarget.Rendering += OnRendering;
         _isRenderingHooked = true;
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;WPF似乎并没有提供直接判断触发设备的方法，这里使用了一个启发式判断逻辑：判断触发间隔时间和偏移值是否为滚轮偏移值的倍数。这一代码在诺尔大佬的&lt;code&gt;EleCho.WpfSuite&lt;/code&gt;中亦有记载。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private bool IsTouchpadScroll(MouseWheelEventArgs e)
{
    var tickCount = Environment.TickCount;
    var isTouchpadScrolling =
            e.Delta % Mouse.MouseWheelDeltaForOneLine != 0 ||
            (tickCount - _lastScrollingTick &amp;lt; 100 &amp;amp;&amp;amp; _lastScrollDelta % Mouse,MouseWheelDeltaForOneLine != 0);
    _lastScrollDelta = e.Delta;
    _lastScrollingTick = e.Timestamp;
    return isTouchpadScrolling;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2.2 适配触摸屏和笔（更新：不建议使用）&lt;/h2&gt;
&lt;p&gt;触摸屏的输入可以通过&lt;code&gt;ManipulationDelta&lt;/code&gt;和&lt;code&gt;ManipulationCompleted&lt;/code&gt;事件来处理。我们将触摸输入映射为滚动偏移量，并使用精确滚动模型，在结束滚动时，可能还有由于快速滑动造成的惯性速率，我们在&lt;code&gt;ManipulationCompleted&lt;/code&gt;中交给惯性滚动模型处理。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;protected override void OnManipulationDelta(ManipulationDeltaEventArgs e)
{
    base.OnManipulationDelta(e);    //如果没有这一行则不会触发ManipulationCompleted事件??
    e.Handled = true;

    //手还在屏幕上，使用精确滚动
    _isAccuracyControl = true;
    double deltaY = -e.DeltaManipulation.Translation.Y;
    _targetOffset = Math.Clamp(_targetOffset + deltaY, 0, ScrollableHeight);
    // 记录最后一次速度
    _lastTouchVelocity = -e.Velocities.LinearVelocity.Y;

    if (!_isRenderingHooked)
    {
        CompositionTarget.Rendering += OnRendering;
        _isRenderingHooked = true;
    }
}

protected override void OnManipulationCompleted(ManipulationCompletedEventArgs e)
{
    base.OnManipulationCompleted(e);
    e.Handled = true;
    Debug.WriteLine(&quot;vel: &quot;+ _lastTouchVelocity);
    _targetVelocity = _lastTouchVelocity; // 用系统识别的速度继续滚动
    _isAccuracyControl = false;

    if (!_isRenderingHooked)
    {
        CompositionTarget.Rendering += OnRendering;
        _isRenderingHooked = true;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;适配笔只需要把笔设备映射为触摸设备即可。这里使用了&lt;code&gt;EleCho.WpfSuite&lt;/code&gt;库中的&lt;code&gt;StylusTouchDevice&lt;/code&gt;来模拟触摸输入，最小可用代码在仓库中给出。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public MyScrollViewer()
{
    //...
    StylusTouchDevice.SetSimulate(this, true);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2.3 OnRender事件&lt;/h2&gt;
&lt;p&gt;在&lt;code&gt;CompositionTarget.Rendering&lt;/code&gt;事件中，我们根据当前模型计算新的滚动位置，并应用到ScrollViewer上。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private void OnRendering(object? sender, EventArgs e)
{
    // 计算时间增量
    long currentTimestamp = Stopwatch.GetTimestamp();
    double deltaTime = (double)(currentTimestamp - _lastTimestamp) / Stopwatch.Frequency;
    _lastTimestamp = currentTimestamp;

    double timeFactor = deltaTime / TargetFrameTime;
    double _currentOffset = VerticalOffset;
    if (_isAccuracyControl)
    {
        // 精确滚动：Lerp 逼近目标（使用时间因子调整）
        double lerpAmount = 1.0 - Math.Pow(1.0 - LerpFactor, timeFactor);
        _currentOffset += (_targetOffset - _currentOffset) * lerpAmount;

        // 如果已经接近目标，就停止
        if (Math.Abs(_targetOffset - _currentOffset) &amp;lt; 0.5)
        {
            _currentOffset = _targetOffset;
            StopRendering();
        }
    }
    else
    {
        // 缓动滚动：速度衰减模拟（使用时间因子调整）
        if (Math.Abs(_targetVelocity) &amp;lt; 0.1)
        {
            _targetVelocity = 0;
            StopRendering();
            return;
        }

        // 使用时间因子调整摩擦力衰减
        _targetVelocity *= Math.Pow(Friction, timeFactor);

        // 根据实际时间计算偏移量
        _currentOffset = Math.Clamp(_currentOffset + _targetVelocity * (timeFactor / 24), 0, ScrollableHeight);
    }

    ScrollToVerticalOffset(_currentOffset);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;三、已知问题&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;使用触摸屏时可能会造成闪烁，因为并没有完全禁用系统的滚动实现。但是如果禁用&lt;code&gt;base.OnManipulationDelta(e)&lt;/code&gt;，则无法触发&lt;code&gt;ManipulationCompleted&lt;/code&gt;事件，导致无法处理惯性滚动。&lt;/li&gt;
&lt;li&gt;&lt;s&gt;尚未测试与ListBox等控件的兼容性。&lt;/s&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;四、完整代码&lt;/h1&gt;
&lt;p&gt;以下是完整的&lt;code&gt;MyScrollViewer&lt;/code&gt;代码，包含了上述所有实现细节。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace FluentScrollViewer;

public class MyScrollViewer : ScrollViewer
{
    #region 模型参数
    /// &amp;lt;summary&amp;gt;
    /// 缓动模型的叠加速度力度，数值越大，滚动起始速率越快，滚得越远
    /// &amp;lt;/summary&amp;gt;
    private const double VelocityFactor = 2.0;
    /// &amp;lt;summary&amp;gt;
    /// 缓动模型的速度衰减系数，数值越小，越快停下来
    /// &amp;lt;/summary&amp;gt;
    private const double Friction = 0.92;

    /// &amp;lt;summary&amp;gt;
    /// 精确模型的插值系数，数值越大，滚动越快接近目标
    /// &amp;lt;/summary&amp;gt;
    private const double LerpFactor = 0.5;

    /// &amp;lt;summary&amp;gt;
    /// 目标帧时间
    /// &amp;lt;/summary&amp;gt;
    private const double TargetFrameTime = 1.0d / 144;
    #endregion

    public MyScrollViewer()
    {
        this.IsManipulationEnabled = true;
        this.PanningMode = PanningMode.VerticalOnly;
        this.IsManipulationEnabled = true;
        //使用此触屏滚动会导致闪屏，先不用了..
        // this.PanningDeceleration = 0; // 禁用默认惯性
        //StylusTouchDevice.SetSimulate(this, true);
        Unloaded += ScrollViewer_Unloaded;
    }
    //记录参数
    private int _lastScrollingTick = 0, _lastScrollDelta = 0;
    //private double _lastTouchVelocity = 0;
    private double _targetOffset = 0;
    private double _targetVelocity = 0;
    private long _lastTimestamp = 0;
    //标志位
    private bool _isRenderingHooked = false;
    private bool _isAccuracyControl = false;

    private void ScrollViewer_Unloaded(object sender, RoutedEventArgs e)
    {
        if (_isRenderingHooked)
        {
            CompositionTarget.Rendering -= OnRendering;
            _isRenderingHooked = false;
        }
    }

    /* protected override void OnManipulationDelta(ManipulationDeltaEventArgs e)
     {
         base.OnManipulationDelta(e);    //如果没有这一行则不会触发ManipulationCompleted事件??
         e.Handled = true;
         //手还在屏幕上，使用精确滚动
         _isAccuracyControl = true;
         double deltaY = -e.DeltaManipulation.Translation.Y;
         _targetOffset = Math.Clamp(_currentOffset + deltaY, 0, ScrollableHeight);
         // 记录最后一次速度
         _lastTouchVelocity = -e.Velocities.LinearVelocity.Y;

         if (!_isRenderingHooked)
         {
             _lastTimestamp = Stopwatch.GetTimestamp();
             CompositionTarget.Rendering += OnRendering;
             _isRenderingHooked = true;
         }
     }

     protected override void OnManipulationCompleted(ManipulationCompletedEventArgs e)
     {
         base.OnManipulationCompleted(e);
         e.Handled = true;
         Debug.WriteLine(&quot;vel: &quot; + _lastTouchVelocity);
         _targetVelocity = _lastTouchVelocity; // 用系统识别的速度继续滚动
         _isAccuracyControl = false;

         if (!_isRenderingHooked)
         {
             _lastTimestamp = Stopwatch.GetTimestamp();
             CompositionTarget.Rendering += OnRendering;
             _isRenderingHooked = true;
         }
     }*/

    /// &amp;lt;summary&amp;gt;
    /// 判断MouseWheel事件由鼠标触发还是由触控板触发
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;param name=&quot;e&quot;&amp;gt;&amp;lt;/param&amp;gt;
    /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
    private bool IsTouchpadScroll(MouseWheelEventArgs e)
    {
        var tickCount = Environment.TickCount;
        var isTouchpadScrolling =
                e.Delta % Mouse.MouseWheelDeltaForOneLine != 0 ||
                (tickCount - _lastScrollingTick &amp;lt; 100 &amp;amp;&amp;amp; _lastScrollDelta % Mouse.MouseWheelDeltaForOneLine != 0);
        //Debug.WriteLine(e.Delta + &quot;  &quot; + e.Timestamp + &quot;  ==&amp;gt;&quot; + isTouchpadScrolling);
        _lastScrollDelta = e.Delta;
        _lastScrollingTick = e.Timestamp;
        return isTouchpadScrolling;
    }

    protected override void OnMouseWheel(MouseWheelEventArgs e)
    {
        e.Handled = true;

        //触摸板使用精确滚动模型
        _isAccuracyControl = IsTouchpadScroll(e);

        if (_isAccuracyControl)
        {
            _targetVelocity = 0; // 防止下一次触发缓动模型时继承没有消除的速度，造成滚动异常
            _targetOffset = Math.Clamp(VerticalOffset - e.Delta, 0, ScrollableHeight);
        }
        else
            _targetVelocity += -e.Delta * VelocityFactor;// 鼠标滚动，叠加速度（惯性滚动）

        if (!_isRenderingHooked)
        {
            _lastTimestamp = Stopwatch.GetTimestamp();
            CompositionTarget.Rendering += OnRendering;
            _isRenderingHooked = true;
        }
    }

    private void OnRendering(object? sender, EventArgs e)
    {
        // 计算时间增量
        long currentTimestamp = Stopwatch.GetTimestamp();
        double deltaTime = (double)(currentTimestamp - _lastTimestamp) / Stopwatch.Frequency;
        _lastTimestamp = currentTimestamp;

        double timeFactor = deltaTime / TargetFrameTime;
        double _currentOffset = VerticalOffset;
        if (_isAccuracyControl)
        {
            // 精确滚动：Lerp 逼近目标（使用时间因子调整）
            double lerpAmount = 1.0 - Math.Pow(1.0 - LerpFactor, timeFactor);
            _currentOffset += (_targetOffset - _currentOffset) * lerpAmount;

            // 如果已经接近目标，就停止
            if (Math.Abs(_targetOffset - _currentOffset) &amp;lt; 0.5)
            {
                _currentOffset = _targetOffset;
                StopRendering();
            }
        }
        else
        {
            // 缓动滚动：速度衰减模拟（使用时间因子调整）
            if (Math.Abs(_targetVelocity) &amp;lt; 0.1)
            {
                _targetVelocity = 0;
                StopRendering();
                return;
            }

            // 使用时间因子调整摩擦力衰减
            _targetVelocity *= Math.Pow(Friction, timeFactor);

            // 根据实际时间计算偏移量
            _currentOffset = Math.Clamp(_currentOffset + _targetVelocity * (timeFactor / 24), 0, ScrollableHeight);
        }

        ScrollToVerticalOffset(_currentOffset);
    }

    private void StopRendering()
    {
        CompositionTarget.Rendering -= OnRendering;
        _isRenderingHooked = false;
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>WPF 使用GDI+提取图片主色调并生成Mica材质特效背景</title><link>https://blog.twlmgatito.com/posts/wpf-mica-image-with-major-color-extract/</link><guid isPermaLink="true">https://blog.twlmgatito.com/posts/wpf-mica-image-with-major-color-extract/</guid><description>将任意图片转为Mica材质背景，并提取主色调作为AccentColor，对UI友好</description><pubDate>Wed, 28 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;先看效果，在浅色模式下：&lt;br /&gt;




在深色模式下：


&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;P.S. 此算法只是尽可能地接近Windows Mica效果，并非实际实现；主色调提取算法只能确保在绝大多数情况下适用。&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;测试项目在Github上开源:&lt;br /&gt;
::github{repo=&quot;TwilightLemon/MicaImageTest&quot;}&lt;/p&gt;
&lt;h1&gt;一、简要原理和设计&lt;/h1&gt;
&lt;h2&gt;1.1 Mica效果&lt;/h2&gt;
&lt;p&gt;Mica效果是Windows 11的一个新特性，旨在为应用程序提供一种更柔和的背景效果。它通过使用桌面壁纸的颜色和纹理来创建一个静态的模糊背景效果。一个大致的模拟过程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;根据颜色模式(浅色或深色)来调整图像对比度&lt;/li&gt;
&lt;li&gt;增加一个白色/黑色的遮罩层&lt;/li&gt;
&lt;li&gt;大半径 高斯模糊处理&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在仓库代码中给出了所有组件的实现，如果你想调整效果，可以修改以下几个值：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static void ApplyMicaEffect(this Bitmap bitmap,bool isDarkmode)
{
    bitmap.AdjustContrast(isDarkmode?-1:-20);//Light Mode通常需要一个更高的对比度
    bitmap.AddMask(isDarkmode);//添加遮罩层
    bitmap.ScaleImage(2);//放大图像(原始图像一般为500x500)以提高输出图像质量
    var rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
    bitmap.GaussianBlur(ref rect, 80f, false);//按需要调整模糊半径
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;1.2 主色调提取与微调&lt;/h2&gt;
&lt;p&gt;从原始图像中提取主色调，主要过程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;像素采样和颜色量化便于统计&lt;/li&gt;
&lt;li&gt;过滤过黑或过白的颜色值(我们会在调整步骤单独处理)&lt;/li&gt;
&lt;li&gt;根据HSL的饱和度和亮度来计算权重，
&lt;ul&gt;
&lt;li&gt;饱和度越高，权重越大&lt;/li&gt;
&lt;li&gt;亮度稳定（我们定为0.6），权重越大&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;选择权重最大的颜色均值作为主色调&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;之后为了适配UI，保证亮度、饱和度适合用于呈现内容，还要对颜色进行微调：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将颜色转为HSL空间&lt;/li&gt;
&lt;li&gt;根据颜色模式调节亮度&lt;/li&gt;
&lt;li&gt;分层调整饱和度，一般来说暗色模式的对比度比亮色模式高&lt;/li&gt;
&lt;li&gt;对特定色相区间（红/绿/蓝/黄）进行差异化调整&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最后计算焦点颜色（FocusAccentColor）只需要根据颜色模式调整亮度即可。&lt;/p&gt;
&lt;h1&gt;二、使用方法&lt;/h1&gt;
&lt;p&gt;将代码仓库中的&lt;code&gt;ImageHelper.cs&lt;/code&gt;添加到项目，然后在需要的地方调用&lt;code&gt;Bitmap&lt;/code&gt;的扩展方法来处理图像。以下是一个简单的示例：&lt;/p&gt;
&lt;p&gt;首先开启项目允许使用UnSafe代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  &amp;lt;PropertyGroup&amp;gt;
    &amp;lt;!-- 允许使用UnSafe代码 --&amp;gt;
    &amp;lt;AllowUnsafeBlocks&amp;gt;true&amp;lt;/AllowUnsafeBlocks&amp;gt;
  &amp;lt;/PropertyGroup&amp;gt;  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;导入本地图像文件，计算主色调、焦点色调并应用Mica效果背景：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; var image=new BitmapImage(new Uri(ImagePath));
 SelectedImg = image;
 var bitmap = image.ToBitmap();
 //major color
 var majorColor = bitmap.GetMajorColor().AdjustColor(IsDarkMode);
 var focusColor = majorColor.ApplyColorMode(IsDarkMode);
 App.Current.Resources[&quot;AccentColor&quot;] = new SolidColorBrush(majorColor);
 App.Current.Resources[&quot;FocusedAccentColor&quot;] = new SolidColorBrush(focusColor);
 //background
 bitmap.ApplyMicaEffect(IsDarkMode);
 BackgroundImg = bitmap.ToBitmapImage();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中，&lt;code&gt;SelectedImg&lt;/code&gt;和&lt;code&gt;BackgroundImg&lt;/code&gt;是绑定到UI的&lt;code&gt;BitmapImage&lt;/code&gt;类型属性，&lt;code&gt;IsDarkMode&lt;/code&gt;是指示当前颜色模式的布尔值。&lt;/p&gt;
&lt;h1&gt;三、注意事项&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;处理大图像时可能会导致性能下降，建议使用较小的图像或在后台线程中处理。&lt;/li&gt;
&lt;li&gt;如果高斯模糊组件报错，请确保Nuget包&lt;code&gt;System.Drawing.Common&lt;/code&gt;的版本为&lt;code&gt;8.0.1&lt;/code&gt;，因为代码中使用了反射获取&lt;code&gt;Bitmap&lt;/code&gt;内部的句柄。&lt;/li&gt;
&lt;li&gt;你可能需要根据实际情况调整模糊半径和对比度等参数，以获得最佳效果。&lt;/li&gt;
&lt;li&gt;库中实现可能并非最佳写法，如果有更好的方法可以提交PR或者评论区见。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最后附上&lt;code&gt;ImageHelper.cs&lt;/code&gt;的完整代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Media.Imaging;

namespace MicaImageTest;

public static class ImageHelper
{
    #region 处理模糊图像
    [DllImport(&quot;gdiplus.dll&quot;, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
    private static extern int GdipBitmapApplyEffect(IntPtr bitmap, IntPtr effect, ref Rectangle rectOfInterest, bool useAuxData, IntPtr auxData, int auxDataSize);
    /// &amp;lt;summary&amp;gt;
    /// 获取对象的私有字段的值
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;typeparam name=&quot;TResult&quot;&amp;gt;字段的类型&amp;lt;/typeparam&amp;gt;
    /// &amp;lt;param name=&quot;obj&quot;&amp;gt;要从其中获取字段值的对象&amp;lt;/param&amp;gt;
    /// &amp;lt;param name=&quot;fieldName&quot;&amp;gt;字段的名称.&amp;lt;/param&amp;gt;
    /// &amp;lt;returns&amp;gt;字段的值&amp;lt;/returns&amp;gt;
    /// &amp;lt;exception cref=&quot;System.InvalidOperationException&quot;&amp;gt;无法找到该字段.&amp;lt;/exception&amp;gt;
    /// 
    internal static TResult GetPrivateField&amp;lt;TResult&amp;gt;(this object obj, string fieldName)
    {
        if (obj == null) return default(TResult);
        Type ltType = obj.GetType();
        FieldInfo lfiFieldInfo = ltType.GetField(fieldName, BindingFlags.GetField | BindingFlags.Instance | BindingFlags.NonPublic);
        if (lfiFieldInfo != null)
            return (TResult)lfiFieldInfo.GetValue(obj);
        else
            throw new InvalidOperationException(string.Format(&quot;Instance field &apos;{0}&apos; could not be located in object of type &apos;{1}&apos;.&quot;, fieldName, obj.GetType().FullName));
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct BlurParameters
    {
        internal float Radius;
        internal bool ExpandEdges;
    }
    [DllImport(&quot;gdiplus.dll&quot;, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
    private static extern int GdipCreateEffect(Guid guid, out IntPtr effect);
    private static Guid BlurEffectGuid = new Guid(&quot;{633C80A4-1843-482B-9EF2-BE2834C5FDD4}&quot;);
    [DllImport(&quot;gdiplus.dll&quot;, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
    private static extern int GdipSetEffectParameters(IntPtr effect, IntPtr parameters, uint size);
    public static IntPtr NativeHandle(this Bitmap Bmp)
    {
        // 通过反射获取Bitmap的私有字段nativeImage的值，该值为GDI+的内部图像句柄
        //新版(8.0.1)Drawing的Nuget包中字段由 nativeImage变更为_nativeImage
        return Bmp.GetPrivateField&amp;lt;IntPtr&amp;gt;(&quot;_nativeImage&quot;);
    }
    [DllImport(&quot;gdiplus.dll&quot;, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
    private static extern int GdipDeleteEffect(IntPtr effect);
    public static void GaussianBlur(this Bitmap Bmp, ref Rectangle Rect, float Radius = 10, bool ExpandEdge = false)
    {
        int Result;
        IntPtr BlurEffect;
        BlurParameters BlurPara;
        if ((Radius &amp;lt; 0) || (Radius &amp;gt; 255))
        {
            throw new ArgumentOutOfRangeException(&quot;半径必须在[0,255]范围内&quot;);
        }
        BlurPara.Radius = Radius;
        BlurPara.ExpandEdges = ExpandEdge;
        Result = GdipCreateEffect(BlurEffectGuid, out BlurEffect);
        if (Result == 0)
        {
            IntPtr Handle = Marshal.AllocHGlobal(Marshal.SizeOf(BlurPara));
            Marshal.StructureToPtr(BlurPara, Handle, true);
            GdipSetEffectParameters(BlurEffect, Handle, (uint)Marshal.SizeOf(BlurPara));
            GdipBitmapApplyEffect(Bmp.NativeHandle(), BlurEffect, ref Rect, false, IntPtr.Zero, 0);
            // 使用GdipBitmapCreateApplyEffect函数可以不改变原始的图像，而把模糊的结果写入到一个新的图像中
            GdipDeleteEffect(BlurEffect);
            Marshal.FreeHGlobal(Handle);
        }
        else
        {
            throw new ExternalException(&quot;不支持的GDI+版本，必须为GDI+1.1及以上版本，且操作系统要求为Win Vista及之后版本.&quot;);
        }
    }
    #endregion

    public static System.Windows.Media.Color GetMajorColor(this Bitmap bitmap)
    {
        int skip = Math.Max(1, Math.Min(bitmap.Width, bitmap.Height) / 100);

        Dictionary&amp;lt;int, ColorInfo&amp;gt; colorMap = [];
        int pixelCount = 0;

        for (int h = 0; h &amp;lt; bitmap.Height; h += skip)
        {
            for (int w = 0; w &amp;lt; bitmap.Width; w += skip)
            {
                Color pixel = bitmap.GetPixel(w, h);

                // 量化颜色 (减少相似颜色的数量)
                int quantizedR = pixel.R / 16 * 16;
                int quantizedG = pixel.G / 16 * 16;
                int quantizedB = pixel.B / 16 * 16;

                // 排除极端黑白色
                int averange = (pixel.R + pixel.G + pixel.B) / 3;
                if (averange &amp;lt; 24) continue;
                if (averange &amp;gt; 230) continue;

                int colorKey = (quantizedR &amp;lt;&amp;lt; 16) | (quantizedG &amp;lt;&amp;lt; 8) | quantizedB;

                if (colorMap.TryGetValue(colorKey, out ColorInfo info))
                {
                    info.Count++;
                    info.SumR += pixel.R;
                    info.SumG += pixel.G;
                    info.SumB += pixel.B;
                }
                else
                {
                    colorMap[colorKey] = new ColorInfo
                    {
                        Count = 1,
                        SumR = pixel.R,
                        SumG = pixel.G,
                        SumB = pixel.B
                    };
                }
                pixelCount++;
            }
        }

        if (pixelCount == 0 || colorMap.Count == 0)
            return System.Windows.Media.Colors.Gray;

        var weightedColors = colorMap.Values.Select(info =&amp;gt;
        {
            float r = info.SumR / (float)info.Count / 255f;
            float g = info.SumG / (float)info.Count / 255f;
            float b = info.SumB / (float)info.Count / 255f;

            // 转换为HSL来检查饱和度和亮度
            RgbToHsl(r, g, b, out float h, out float s, out float l);

            // 颜色越饱和越有可能是主色调，过亮或过暗的颜色权重降低
            float weight = info.Count * s * (1 - Math.Abs(l - 0.6f) * 1.8f);

            return new
            {
                R = info.SumR / info.Count,
                G = info.SumG / info.Count,
                B = info.SumB / info.Count,
                Weight = weight
            };
        })
        .OrderByDescending(c =&amp;gt; c.Weight);

        if (weightedColors.First() is { } dominantColor)
        {
            // 取权重最高的颜色
            return System.Windows.Media.Color.FromRgb(
                (byte)dominantColor.R,
                (byte)dominantColor.G,
                (byte)dominantColor.B);
        }

        return System.Windows.Media.Colors.Gray;
    }

    private class ColorInfo
    {
        public int Count { get; set; }
        public int SumR { get; set; }
        public int SumG { get; set; }
        public int SumB { get; set; }
    }

    public static System.Windows.Media.Color AdjustColor(this System.Windows.Media.Color col, bool isDarkMode)
    {
        // 转换为HSL色彩空间，便于调整亮度和饱和度
        RgbToHsl(col.R / 255f, col.G / 255f, col.B / 255f, out float h, out float s, out float l);

        bool isNearGrayscale = s &amp;lt; 0.15f; // 判断是否接近灰度

        // 1. 基于UI模式进行初步亮度调整
        if (isDarkMode)
        {
            // 在暗色模式下，避免颜色过暗，提高整体亮度
            if (l &amp;lt; 0.5f)
                l = 0.3f + l * 0.5f;

            if (isNearGrayscale)
                l = Math.Max(l, 0.4f); // 确保足够明亮
        }
        else
        {
            // 在亮色模式下，避免颜色过亮，降低整体亮度
            if (l &amp;gt; 0.5f)
                l = 0.3f + l * 0.4f;

            if (isNearGrayscale)
                l = Math.Min(l, 0.6f); // 确保不过亮
        }

        // 2. 调整饱和度
        if (!isNearGrayscale)
        {
            if (s &amp;gt; 0.7f)
            {
                // 高饱和度降低，但是暗色模式需要更鲜明的颜色
                s = isDarkMode ? 0.7f - (s - 0.7f) * 0.2f : 0.65f - (s - 0.7f) * 0.4f;
            }
            else if (s &amp;gt; 0.4f)
            {
                // 中等饱和度微调
                s = isDarkMode ? s * 0.85f : s * 0.75f;
            }
            else if (s &amp;gt; 0.1f) // 低饱和度但不是接近灰度
            {
                // 低饱和度增强，尤其在暗色模式下
                s = isDarkMode ? Math.Min(0.5f, s * 1.5f) : Math.Min(0.4f, s * 1.3f);
            }
        }

        // 3. 特殊色相区域的处理
        if (!isNearGrayscale) // 仅处理有明显色相的颜色
        {
            // 红色区域 (0-30° 或 330-360°)
            if ((h &amp;lt;= 0.08f) || (h &amp;gt;= 0.92f))
            {
                if (isDarkMode)
                {
                    // 暗色模式下红色需要更高饱和度和亮度
                    s = Math.Min(0.7f, s * 1.1f);
                    l = Math.Min(0.8f, l * 1.15f);
                }
                else
                {
                    // 亮色模式下红色降低饱和度，避免刺眼
                    s *= 0.8f;
                    l = Math.Max(0.4f, l * 0.9f);
                }
            }
            // 绿色区域 (90-150°)
            else if (h &amp;gt;= 0.25f &amp;amp;&amp;amp; h &amp;lt;= 0.42f)
            {
                if (isDarkMode)
                {
                    // 暗色模式下绿色提高亮度，降低饱和度，避免荧光感
                    s *= 0.85f;
                    l = Math.Min(0.7f, l * 1.2f);
                }
                else
                {
                    // 亮色模式下绿色降低饱和度更多
                    s *= 0.75f;
                }
            }
            // 蓝色区域 (210-270°)
            else if (h &amp;gt;= 0.58f &amp;amp;&amp;amp; h &amp;lt;= 0.75f)
            {
                if (isDarkMode)
                {
                    // 暗色模式下蓝色提高亮度和饱和度
                    s = Math.Min(0.85f, s * 1.2f);
                    l = Math.Min(0.7f, l * 1.25f);
                }
                else
                {
                    // 亮色模式下蓝色保持中等饱和度
                    s = Math.Min(0.7f, Math.Max(0.4f, s));
                }
            }
            // 黄色区域 (30-90°)
            else if (h &amp;gt; 0.08f &amp;amp;&amp;amp; h &amp;lt; 0.25f)
            {
                if (isDarkMode)
                {
                    // 暗色模式下黄色需要降低饱和度，提高亮度
                    s *= 0.8f;
                    l = Math.Min(0.75f, l * 1.2f);
                }
                else
                {
                    // 亮色模式下黄色大幅降低饱和度
                    s *= 0.7f;
                    l = Math.Max(0.5f, l * 0.9f);
                }
            }
        }



        // 5. 最终亮度修正 - 确保在各种UI模式下都有足够的对比度
        if (isDarkMode &amp;amp;&amp;amp; l &amp;lt; 0.3f) l = 0.3f; // 暗色模式下确保最小亮度
        if (!isDarkMode &amp;amp;&amp;amp; l &amp;gt; 0.7f) l = 0.7f; // 亮色模式下确保最大亮度

        // 转换回RGB
        HslToRgb(h, s, l, out float r, out float g, out float b);

        // 确保RGB值在有效范围内
        byte R = (byte)Math.Max(0, Math.Min(255, r * 255));
        byte G = (byte)Math.Max(0, Math.Min(255, g * 255));
        byte B = (byte)Math.Max(0, Math.Min(255, b * 255));

        return System.Windows.Media.Color.FromRgb(R, G, B);
    }
    public static System.Windows.Media.Color ApplyColorMode(this System.Windows.Media.Color color,bool isDarkMode)
    {
        RgbToHsl(color.R/255f,color.G/255f, color.B/255f,out float h, out float s, out float l);
        if (isDarkMode)
            l = Math.Max(0.05f, l - 0.1f);
        else
            l = Math.Min(0.95f, l + 0.1f);

        HslToRgb(h, s, l, out float r, out float g, out float b);
        return System.Windows.Media.Color.FromRgb((byte)(r * 255), (byte)(g * 255), (byte)(b * 255));
    }

    private static void RgbToHsl(float r, float g, float b, out float h, out float s, out float l)
    {
        float max = Math.Max(r, Math.Max(g, b));
        float min = Math.Min(r, Math.Min(g, b));

        // 计算亮度
        l = (max + min) / 2.0f;

        // 默认值初始化
        h = 0;
        s = 0;

        if (max == min)
        {
            // 无色调 (灰色)
            return;
        }

        float d = max - min;

        // 计算饱和度
        s = l &amp;gt; 0.5f ? d / (2.0f - max - min) : d / (max + min);

        // 计算色相
        if (max == r)
        {
            h = (g - b) / d + (g &amp;lt; b ? 6.0f : 0.0f);
        }
        else if (max == g)
        {
            h = (b - r) / d + 2.0f;
        }
        else // max == b
        {
            h = (r - g) / d + 4.0f;
        }

        h /= 6.0f;

        // 确保h在[0,1]范围内
        h = Math.Max(0, Math.Min(1, h));
    }

    private static void HslToRgb(float h, float s, float l, out float r, out float g, out float b)
    {
        // 确保h在[0,1]范围内
        h = ((h % 1.0f) + 1.0f) % 1.0f;

        // 确保s和l在[0,1]范围内
        s = Math.Max(0, Math.Min(1, s));
        l = Math.Max(0, Math.Min(1, l));

        if (s == 0.0f)
        {
            // 灰度颜色
            r = g = b = l;
            return;
        }

        float q = l &amp;lt; 0.5f ? l * (1.0f + s) : l + s - l * s;
        float p = 2.0f * l - q;

        r = HueToRgb(p, q, h + 1.0f / 3.0f);
        g = HueToRgb(p, q, h);
        b = HueToRgb(p, q, h - 1.0f / 3.0f);
    }

    private static float HueToRgb(float p, float q, float t)
    {
        // 确保t在[0,1]范围内
        t = ((t % 1.0f) + 1.0f) % 1.0f;

        if (t &amp;lt; 1.0f / 6.0f)
            return p + (q - p) * 6.0f * t;
        if (t &amp;lt; 0.5f)
            return q;
        if (t &amp;lt; 2.0f / 3.0f)
            return p + (q - p) * (2.0f / 3.0f - t) * 6.0f;
        return p;
    }
    public static BitmapImage ToBitmapImage(this Bitmap Bmp)
    {
        BitmapImage BmpImage = new();
        using (MemoryStream lmemStream = new())
        {
            Bmp.Save(lmemStream, ImageFormat.Png);
            BmpImage.BeginInit();
            BmpImage.StreamSource = new MemoryStream(lmemStream.ToArray());
            BmpImage.EndInit();
        }
        return BmpImage;
    }

    public static Bitmap ToBitmap(this BitmapImage img){
        using MemoryStream outStream = new();
        BitmapEncoder enc = new PngBitmapEncoder();
        enc.Frames.Add(BitmapFrame.Create(img));
        enc.Save(outStream);
        return new Bitmap(outStream);
    }

    public static void AddMask(this Bitmap bitmap,bool darkmode)
    {
        var color1 = darkmode ? Color.FromArgb(150, 0, 0, 0) : Color.FromArgb(160, 255, 255, 255);
        var color2 = darkmode ? Color.FromArgb(180, 0, 0, 0) : Color.FromArgb(200, 255, 255, 255);
        using Graphics g = Graphics.FromImage(bitmap);
        using LinearGradientBrush brush = new(
            new Rectangle(0, 0, bitmap.Width, bitmap.Height),
            color1,
            color2,
            LinearGradientMode.Vertical);
        g.FillRectangle(brush, new Rectangle(0, 0, bitmap.Width, bitmap.Height));
    }
    public static void AdjustContrast(this Bitmap bitmap, float contrast)
    {
        contrast = (100.0f + contrast) / 100.0f;
        contrast *= contrast;

        BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),
            ImageLockMode.ReadWrite, bitmap.PixelFormat);

        int width = bitmap.Width;
        int height = bitmap.Height;

        unsafe
        {
            for (int y = 0; y &amp;lt; height; y++)
            {
                byte* row = (byte*)data.Scan0 + (y * data.Stride);
                for (int x = 0; x &amp;lt; width; x++)
                {
                    int idx = x * 3;

                    float blue = row[idx] / 255.0f;
                    float green = row[idx + 1] / 255.0f;
                    float red = row[idx + 2] / 255.0f;

                    // 转换为HSL
                    RgbToHsl(red, green, blue, out float h, out float s, out float l);

                    // 调整亮度以增加对比度
                    l = (((l - 0.5f) * contrast) + 0.5f);

                    // 转换回RGB
                    HslToRgb(h, s, l, out red, out green, out blue);

                    row[idx] = (byte)Math.Max(0, Math.Min(255, blue * 255.0f));
                    row[idx + 1] = (byte)Math.Max(0, Math.Min(255, green * 255.0f));
                    row[idx + 2] = (byte)Math.Max(0, Math.Min(255, red * 255.0f));
                }
            }
        }

        bitmap.UnlockBits(data);
    }

    public static void ScaleImage(this Bitmap bitmap, double scale)
    {
        // 计算新的尺寸
        int newWidth = (int)(bitmap.Width * scale);
        int newHeight = (int)(bitmap.Height * scale);

        // 创建目标位图
        Bitmap newBitmap = new Bitmap(newWidth, newHeight, bitmap.PixelFormat);

        // 设置高质量绘图参数
        using (Graphics graphics = Graphics.FromImage(newBitmap))
        {
            graphics.CompositingQuality = CompositingQuality.HighQuality;
            graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
            graphics.SmoothingMode = SmoothingMode.HighQuality;
            graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;

            // 绘制缩放后的图像
            graphics.DrawImage(bitmap,
                new Rectangle(0, 0, newWidth, newHeight),
                new Rectangle(0, 0, bitmap.Width, bitmap.Height),
                GraphicsUnit.Pixel);
        }
        bitmap = newBitmap;
    }

    public static void ApplyMicaEffect(this Bitmap bitmap,bool isDarkmode)
    {
        bitmap.AdjustContrast(isDarkmode?-1:-20);
        bitmap.AddMask(isDarkmode);
        bitmap.ScaleImage(2);
        var rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
        bitmap.GaussianBlur(ref rect, 80f, false);
    }
}

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Manuscript for &quot;Identity-Based Encryption from the Weil Pairing&quot;</title><link>https://blog.twlmgatito.com/posts/manuscript-for-ibe-with-weil-pairing/</link><guid isPermaLink="true">https://blog.twlmgatito.com/posts/manuscript-for-ibe-with-weil-pairing/</guid><pubDate>Tue, 04 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Paper reference: &lt;a href=&quot;https://link.springer.com/chapter/10.1007/3-540-44647-8_13&quot;&gt;Identity-Based Encryption from the Weil Pairing | SpringerLink&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Known Public Encryption Scheme&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Setup: Generate  global system parameters and a &lt;code&gt;master-key&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Extract: use the &lt;code&gt;master-key&lt;/code&gt; to generate the &lt;code&gt;private-key&lt;/code&gt;  corresponding to an &lt;code&gt;public-key&lt;/code&gt; string  $ID ∈ {0,1}^*$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enc: encrypt M with ID → C&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Dec: decrypt C with private-key&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Application&lt;/h1&gt;
&lt;p&gt;(interesting)&lt;/p&gt;
&lt;h2&gt;Revocation of Public Keys&lt;/h2&gt;
&lt;p&gt;&amp;lt;span class=&quot;highlight&quot; data-annotation=&quot;%7B%22attachmentURI%22%3A%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FBQTNBP2M%22%2C%22pageLabel%22%3A%22215%22%2C%22position%22%3A%7B%22pageIndex%22%3A2%2C%22rects%22%3A%5B%5B149.70940037000003%2C484.58142187000004%2C480.66033437%2C494.54442187%5D%2C%5B134.76490037000002%2C472.62582187000004%2C480.61540123999987%2C482.58882187%5D%2C%5B134.76480073999997%2C460.67022187000003%2C261.2849377400001%2C470.63322187%5D%5D%7D%2C%22citationItem%22%3A%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FD9XP6B7N%22%5D%2C%22locator%22%3A%22215%22%7D%7D&quot; ztype=&quot;zhighlight&quot;&amp;gt;&amp;lt;a href=&quot;zotero://open/library/items/BQTNBP2M?page=3&quot;&amp;gt;“One could potentially make this approach more granular by encrypting e-mail for Bob using “bob@hotmail.com ‖ current-date”. This forces Bob to obtain a new private key every day.”&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span class=&quot;citation&quot; data-citation=&quot;%7B%22citationItems%22%3A%5B%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FD9XP6B7N%22%5D%2C%22locator%22%3A%22215%22%7D%5D%2C%22properties%22%3A%7B%7D%7D&quot; ztype=&quot;zcitation&quot;&amp;gt;(&amp;lt;span class=&quot;citation-item&quot;&amp;gt;&amp;lt;a href=&quot;zotero://select/library/items/D9XP6B7N&quot;&amp;gt;Boneh and Franklin, p. 215&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;)&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;span class=&quot;highlight&quot; data-annotation=&quot;%7B%22attachmentURI%22%3A%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FBQTNBP2M%22%2C%22pageLabel%22%3A%22215%22%2C%22position%22%3A%7B%22pageIndex%22%3A2%2C%22rects%22%3A%5B%5B381.72764474%2C400.89222187%2C480.7399387399999%2C410.85522187%5D%2C%5B134.76480073999997%2C388.93662187%2C480.66023474%2C398.89962187%5D%2C%5B134.76480073999997%2C376.98102187%2C291.50271674%2C386.94402187%5D%5D%7D%2C%22citationItem%22%3A%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FD9XP6B7N%22%5D%2C%22locator%22%3A%22215%22%7D%7D&quot; ztype=&quot;zhighlight&quot;&amp;gt;&amp;lt;a href=&quot;zotero://open/library/items/BQTNBP2M?page=3&quot;&amp;gt;“This approach enables Alice to send messages into the future: Bob will only be able to decrypt the e-mail on the date specified by Alice”&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span class=&quot;citation&quot; data-citation=&quot;%7B%22citationItems%22%3A%5B%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FD9XP6B7N%22%5D%2C%22locator%22%3A%22215%22%7D%5D%2C%22properties%22%3A%7B%7D%7D&quot; ztype=&quot;zcitation&quot;&amp;gt;(&amp;lt;span class=&quot;citation-item&quot;&amp;gt;&amp;lt;a href=&quot;zotero://select/library/items/D9XP6B7N&quot;&amp;gt;Boneh and Franklin, p. 215&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;)&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;h2&gt;Delegation of Decryption Keys&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;bind &amp;lt;pk,sk&amp;gt; pairs with dates so that private-key can be stored in a vulnerable device: only thoses pairs are compromised and the Master-key is unharmed.&lt;/li&gt;
&lt;li&gt;delegations of duties or other title as a skill to distribute private keys.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Construction&lt;/h1&gt;
&lt;h2&gt;preparation&lt;/h2&gt;
&lt;h3&gt;Bilinear Map:  e.g. Weil pairing&lt;/h3&gt;
&lt;p&gt;$$
e: G_1 \times G_1 \rightarrow G_2
$$&lt;/p&gt;
&lt;p&gt;, where $G_1$ and $G_2$ are two CYCLIC groups of some large prime order $p$ .&lt;/p&gt;
&lt;p&gt;&amp;lt;span class=&quot;highlight&quot; data-annotation=&quot;%7B%22attachmentURI%22%3A%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FBQTNBP2M%22%2C%22pageLabel%22%3A%22216%22%2C%22position%22%3A%7B%22pageIndex%22%3A3%2C%22rects%22%3A%5B%5B175.40282900000003%2C431.93851%2C480.096666%2C442.64928%5D%2C%5B134.765%2C418.29671934000004%2C480.64607099999995%2C432.06751%5D%2C%5B134.75561850000005%2C408.02853%2C264.157754%2C418.73827%5D%5D%7D%2C%22citationItem%22%3A%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FD9XP6B7N%22%5D%2C%22locator%22%3A%22216%22%7D%7D&quot; ztype=&quot;zhighlight&quot;&amp;gt;&amp;lt;a href=&quot;zotero://open/library/items/BQTNBP2M?page=4&quot;&amp;gt;“In our system, G1 is the group of points of an elliptic curve over Fp and G2 is a subgroup of F∗p2 . Therefore, we view G1 as an additive group and G2 as a multiplicative group.”&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span class=&quot;citation&quot; data-citation=&quot;%7B%22citationItems%22%3A%5B%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FD9XP6B7N%22%5D%2C%22locator%22%3A%22216%22%7D%5D%2C%22properties%22%3A%7B%7D%7D&quot; ztype=&quot;zcitation&quot;&amp;gt;(&amp;lt;span class=&quot;citation-item&quot;&amp;gt;&amp;lt;a href=&quot;zotero://select/library/items/D9XP6B7N&quot;&amp;gt;Boneh and Franklin, p. 216&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;)&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Bilinear  $ e(aP,bQ) =e(P,Q)^{ab} ; for ; all ; P,Q∈G_1 ; and ; all ; a,b ∈ Z$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DH problem is hard in  $G_1$&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Properties of the Weil Pairing&lt;/h3&gt;
&lt;p&gt;Build:&lt;/p&gt;
&lt;p&gt;$$
E:; y^2=x^3+1 ; over ; \mathbb{F}_p,
$$&lt;/p&gt;
&lt;p&gt;where prime $p$ satisfies $ p =2 ; (mod ; 3) ; and ; p=6q-1 ; for ; some ; prime ; q$&lt;/p&gt;
&lt;h2&gt;Syntax&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Setup&lt;/strong&gt;:  k -&amp;gt; sys param (publicly shared) | master key (owned by PKG (&amp;lt;span class=&quot;highlight&quot; data-annotation=&quot;%7B%22attachmentURI%22%3A%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FBQTNBP2M%22%2C%22pageLabel%22%3A%22216%22%2C%22position%22%3A%7B%22pageIndex%22%3A3%2C%22rects%22%3A%5B%5B194.46929001%2C189.19395407000002%2C303.8032520100001%2C199.15695407%5D%5D%7D%2C%22citationItem%22%3A%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FD9XP6B7N%22%5D%2C%22locator%22%3A%22216%22%7D%7D&quot; ztype=&quot;zhighlight&quot;&amp;gt;&amp;lt;a href=&quot;zotero://open/library/items/BQTNBP2M?page=4&quot;&amp;gt;“Private Key Generator”&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt; ))&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**Extract(master-key K,string ID) **=&amp;gt; private decryption key d.&amp;gt; ID is used as a public key&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Enc/Dec&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Secure Models&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;IND-ID-CCA: &amp;lt;span class=&quot;highlight&quot; data-annotation=&quot;%7B%22attachmentURI%22%3A%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FBQTNBP2M%22%2C%22pageLabel%22%3A%22218%22%2C%22position%22%3A%7B%22pageIndex%22%3A5%2C%22rects%22%3A%5B%5B411.2665887000001%2C530.4193803699999%2C480.6289947000001%2C540.38238037%5D%2C%5B134.75348670000005%2C518.4637803699999%2C210.6615837000001%2C528.42678037%5D%5D%7D%2C%22citationItem%22%3A%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FD9XP6B7N%22%5D%2C%22locator%22%3A%22218%22%7D%7D&quot; ztype=&quot;zhighlight&quot;&amp;gt;&amp;lt;a href=&quot;zotero://open/library/items/BQTNBP2M?page=6&quot;&amp;gt;“adaptive chosen ciphertext attack”&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span class=&quot;citation&quot; data-citation=&quot;%7B%22citationItems%22%3A%5B%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FD9XP6B7N%22%5D%2C%22locator%22%3A%22218%22%7D%5D%2C%22properties%22%3A%7B%7D%7D&quot; ztype=&quot;zcitation&quot;&amp;gt;(&amp;lt;span class=&quot;citation-item&quot;&amp;gt;&amp;lt;a href=&quot;zotero://select/library/items/D9XP6B7N&quot;&amp;gt;Boneh and Franklin, p. 218&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;)&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ID-OWE(one-way encryption): Given random public key  $K_{pub}$  and ciphertext C which is the encryption of a random message M using  $K_{pub}$  , A’s goal is to recover M.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;models above allow A to conduct multi-round queries of &amp;lt;ID,d(private key)&amp;gt; pairs;&lt;/p&gt;
&lt;h2&gt;Scheme&lt;/h2&gt;
&lt;h3&gt;MapToPoint(string ID)=&amp;gt; Point&lt;/h3&gt;
&lt;p&gt;$G: ; {0,1}^* \rightarrow \mathbb{F}_b$ , where in the security analysis  G is viewed as a &lt;code&gt;random oracle&lt;/code&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Compute  $ y_0 = G(ID)$   and  $ y_0 \rightarrow E:;x_0=(y^2_0 -1)^{1/3} =(y^2_0-1)^{(2p-1)/3};mod;p$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;return  $Q_{ID} = 6(x_0,y_0) ∈ E/\mathbb{F}_b$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Basic IBE (BasicIdent)&lt;/h3&gt;
&lt;p&gt;security parameter: $k$&lt;/p&gt;
&lt;h4&gt;Setup&lt;/h4&gt;
&lt;p&gt;Step 1: Choose a large k-bit prime p such that p = 2 mod 3 and p = 6q − 1 for some prime q &amp;gt; 3. Let E be the elliptic curve defined by $y^2 = x^3 + 1$  over $\mathbb{F}_b$ . Choose an arbitrary $P ∈ E/\mathbb{F}_p$  of order q.&lt;/p&gt;
&lt;p&gt;Step 2: Pick a random $s ∈ Z^*&lt;em&gt;q$  and set $P&lt;/em&gt;{pub} = sP$ .&lt;/p&gt;
&lt;p&gt;Step 3: Choose a cryptographic hash function $H: ; F_{p^2} → {0, 1}^n$  for some n. Choose a cryptographic hash function $G: ; {0, 1}^∗ → \mathbb{F}_p$. The security analysis will view H and G as random oracles.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;output: system params :=$&amp;lt;p,n,P,P_{pub},G,H&amp;gt;$ , master-key : $s∈\mathbb{Z}_q$ picked in Step 2.&lt;/p&gt;
&lt;p&gt;Message space is $ M ={0,1}^n$&lt;/p&gt;
&lt;p&gt;Ciphertext space is $ C= E/\mathbb{F}_b \times {0,1}^n$&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;Extract(string ID)&lt;/h4&gt;
&lt;p&gt;Step 1. $ Q_{ID} = MapToPoint(ID)$&lt;/p&gt;
&lt;p&gt;Step 2. private key $d_{ID}=sQ_{ID}$, where s is the master key.&lt;/p&gt;
&lt;h4&gt;Encrypt&lt;/h4&gt;
&lt;p&gt;$$
Q_{ID} = MapToPoint(ID)
$$&lt;/p&gt;
&lt;p&gt;$$
r \xleftarrow{\text$} \mathbb{Z}_q
$$&lt;/p&gt;
&lt;p&gt;$$
C=&amp;lt;rP,M \oplus H(g^r_{ID})&amp;gt; ; where ; g_{ID}=e(Q_{ID},P_{pub})
$$&lt;/p&gt;
&lt;h4&gt;Decrypt&lt;/h4&gt;
&lt;p&gt;&amp;lt;span class=&quot;highlight&quot; data-annotation=&quot;%7B%22attachmentURI%22%3A%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FBQTNBP2M%22%2C%22pageLabel%22%3A%22222%22%2C%22position%22%3A%7B%22pageIndex%22%3A9%2C%22rects%22%3A%5B%5B182.47192882999997%2C546.8013%2C480.63134203999965%2C556.7643%5D%2C%5B143.26931716999997%2C534.09952%2C480.59374037000003%2C544.80929%5D%2C%5B143.26549007000006%2C522.64177%2C339.58197999999993%2C532.85427%5D%5D%7D%2C%22citationItem%22%3A%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FD9XP6B7N%22%5D%2C%22locator%22%3A%22222%22%7D%7D&quot; ztype=&quot;zhighlight&quot;&amp;gt;&amp;lt;a href=&quot;zotero://open/library/items/BQTNBP2M?page=10&quot;&amp;gt;“Let C = 〈U, V 〉 ∈ C be a ciphertext encrypted using the public key ID. If U ∈ E/Fp is not a point of order q reject the ciphertext. Otherwise, to decrypt C using the private key dID compute:”&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span class=&quot;citation&quot; data-citation=&quot;%7B%22citationItems%22%3A%5B%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FD9XP6B7N%22%5D%2C%22locator%22%3A%22222%22%7D%5D%2C%22properties%22%3A%7B%7D%7D&quot; ztype=&quot;zcitation&quot;&amp;gt;(&amp;lt;span class=&quot;citation-item&quot;&amp;gt;&amp;lt;a href=&quot;zotero://select/library/items/D9XP6B7N&quot;&amp;gt;Boneh and Franklin, p. 222&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;)&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;p&gt;$$
M = V \oplus H(e(d_{ID},U))
$$&lt;/p&gt;
&lt;p&gt;proof:&lt;/p&gt;
&lt;p&gt;$$
e(d_{ID},U)=e(d_{ID},rP) =e(Q_{ID},P)^{sr} = e(Q_{ID},P_{pub}), ; where ; P_{pub}=sP ; is ; sys-param
$$&lt;/p&gt;
&lt;h3&gt;IND-CCA Security: enhanced by Fujisaki-Okamoto transform&lt;/h3&gt;
&lt;h4&gt;Fujisaki-Okamoto transform&lt;/h4&gt;
&lt;p&gt;Suppose $&amp;lt;PEnc,PDec&amp;gt;$ is a public key encryption scheme.H and G are hash functions(viewed as random oracles): $ H: {0,1}^n \times {0,1}^n \rightarrow \mathbb{F}_q$ , $G: {0,1}^n \rightarrow {0,1}^n$&lt;/p&gt;
&lt;p&gt;$$
\sigma \xleftarrow{\text$} Key ; Domain ; of ; H
$$&lt;/p&gt;
&lt;p&gt;$$
Enc_{FO}: C_K = PEnc(pk,\sigma; H(\sigma,M)) , ; C_M= G(\sigma) \oplus M
$$&lt;/p&gt;
&lt;p&gt;$$
Dec_{FO}: \sigma = PDec(sk,C_K), ; verify ; C_K == Enc_{FO}(·).C_K ; then ; M= C_M \oplus G(\sigma)
$$&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Comparing with conventional Hybrid Encryption, FO transform  surpasses as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IND-CCA Security&lt;/li&gt;
&lt;li&gt;using Hash functions to blind random seeds and message&lt;/li&gt;
&lt;li&gt;check keys before decryption&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;Encrypt&lt;/h4&gt;
&lt;p&gt;$$
Q_{ID} = MapToPoint(ID)
$$&lt;/p&gt;
&lt;p&gt;$$
\sigma \xleftarrow{\text$} {0,1}^n, ; then ; r=H(\sigma,M)
$$&lt;/p&gt;
&lt;p&gt;$$
C=&amp;lt;rP,\sigma \oplus H(g^r_{ID}), M \oplus G(\sigma)&amp;gt;
$$&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Notice that $g^r_{ID} =e(Q_{ID,P_{pub}})^r$  binds with both the message M and the random seed $\sigma$ ,functioning as $H(\sigma,M)$ as that above.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;Decrypt: receive C=&amp;lt;U,V,W&amp;gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Check if  $U ∈ E/\mathbb{F}_p$  is not a point of order q, otherwise reject C&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compute  $ \sigma = V \oplus H(e(d_{ID},U))$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Decrypt  $ M=W \oplus G(\sigma)$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;re-compute and check   $r=H(\sigma,M) == U =rP$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>Manuscript for &quot;Public Key Encryption with Keyword Search&quot;</title><link>https://blog.twlmgatito.com/posts/manuscript-for-peks/</link><guid isPermaLink="true">https://blog.twlmgatito.com/posts/manuscript-for-peks/</guid><pubDate>Mon, 03 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Paper reference: &lt;a href=&quot;https://link.springer.com/chapter/10.1007/978-3-540-24676-3_30&quot;&gt;Public Key Encryption with Keyword Search | SpringerLink&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;PEKS using Bilinear Maps&lt;/h1&gt;
&lt;h2&gt;Construction&lt;/h2&gt;
&lt;h3&gt;素数p阶群, （乘法群）定义双线性映射：&lt;/h3&gt;
&lt;p&gt;非退化性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$ e(g,g) \rightarrow g&apos; , ; where ; g&apos; ; is ; a ; generator ; of ; G_2$&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Hash functions:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;$ H_1: {0,1}^* \rightarrow G_1$&lt;/li&gt;
&lt;li&gt;$ H_2 : G_2 \rightarrow {0,1}^{\log{p}}$&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;PEKS Scheme&lt;/h2&gt;
&lt;p&gt;$$
PEKS ; Scheme:=(KeyGen,PEKS,Trapdoor,Test)
$$&lt;/p&gt;
&lt;h3&gt;KeyGen:&lt;/h3&gt;
&lt;p&gt;input 安全参数$\lambda$ , 群阶p, $G_1$ ,$G_2$ ，$$ \alpha \xleftarrow{\text$} Z^*_p$$, g是 $G_1$ 的一个生成元&lt;/p&gt;
&lt;p&gt;output $ A_{pub} = [g,h=g^\alpha] ; and ; A_{priv} =\alpha$&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一个典型的RSA公私钥分发&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;PEKS($A_{pub},W$):&lt;/h3&gt;
&lt;p&gt;$$ r \xleftarrow{\text$} Z^*_p$$
, then compute $ t = e(H_1(W),h^r)$&lt;/p&gt;
&lt;p&gt;output $S=[g^r,H_2(t)]$&lt;/p&gt;
&lt;h3&gt;$ Trapdoor(A_{priv},W) =&amp;gt; T_W =H_1(W)^\alpha$&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Notice that $ t ∈ G_2$  and $ T_W ∈ G_1$&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Test($A_{pub}$, S, $T_W$)&lt;/h3&gt;
&lt;p&gt;S=[A,B]&lt;/p&gt;
&lt;p&gt;output $H_2(e(T_W,A)) == B$&lt;/p&gt;
&lt;h3&gt;proof:&lt;/h3&gt;
&lt;p&gt;$$
H_2(e(T_W,A)) = H_2(e(H_1(W)^\alpha,g^r)) = H_2(e(H_1(W),g)^{\alpha r})
$$&lt;/p&gt;
&lt;p&gt;$$
H_2(t) = H_2(e(H_1(W),h^r)) = H_2(e(H_1(W),g^{\alpha r}))= H_2(e(H_1(W),g)^{\alpha r})
$$&lt;/p&gt;
&lt;h2&gt;BDH Assumption&lt;/h2&gt;
&lt;p&gt;&amp;lt;span class=&quot;highlight&quot; data-annotation=&quot;%7B%22attachmentURI%22%3A%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2F263DSXDA%22%2C%22pageLabel%22%3A%22512%22%2C%22position%22%3A%7B%22pageIndex%22%3A6%2C%22rects%22%3A%5B%5B134.76133%2C350.8833972%2C480.50933480000003%2C360.5445306%5D%2C%5B134.76129%2C339.0033972%2C480.6450542%2C350.71035%5D%2C%5B134.76151%2C327.0033972%2C480.55128557999967%2C336.66458059999997%5D%2C%5B134.7604%2C315.9373418%2C296.1176583800001%2C324.78413059999997%5D%5D%7D%2C%22citationItem%22%3A%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FKYUKPHCB%22%5D%2C%22locator%22%3A%22512%22%7D%7D&quot; ztype=&quot;zhighlight&quot;&amp;gt;&amp;lt;a href=&quot;zotero://open/library/items/263DSXDA?page=7&quot;&amp;gt;“Bilinear Diffie-Hellman Problem (BDH): Fix a generator g of G1. The BDH problem is as follows: given g, ga, gb, gc ∈ G1 as input, compute e(g, g)abc ∈ G2. We say that BDH is intractable if all polynomial time algorithms have a negligible advantage in solving BDH.”&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span class=&quot;citation&quot; data-citation=&quot;%7B%22citationItems%22%3A%5B%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FKYUKPHCB%22%5D%2C%22locator%22%3A%22512%22%7D%5D%2C%22properties%22%3A%7B%7D%7D&quot; ztype=&quot;zcitation&quot;&amp;gt;(&amp;lt;span class=&quot;citation-item&quot;&amp;gt;&amp;lt;a href=&quot;zotero://select/library/items/KYUKPHCB&quot;&amp;gt;Boneh et al., 2004, p. 512&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;)&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;h2&gt;安全性证明&lt;/h2&gt;
&lt;h3&gt;定理1. 非交互式PEKS在&lt;code&gt;适应性选择关键词攻击&lt;/code&gt;下具有语义安全，如果BDH是难解问题。&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;安全目标&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;/strong&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;：在自适应选择关键词攻击下语义安全。&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;​&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;strong&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;归约到BDH问题&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;/strong&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;：&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;假设存在攻击者 &amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;em&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;A&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;/em&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt; 能以优势 &amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;em&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;ϵ&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;/em&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt; 区分关键词加密，构造算法 &amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;em&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;B&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;/em&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt; 利用 &amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;em&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;A&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;/em&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt; 解决BDH问题。&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;​&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;strong&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;模拟过程&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;/strong&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;：&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;B&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;/em&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt; 接收BDH挑战 &amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt; $(g,g^α,g^β,g^γ)$ &amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;，模拟公钥 &amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt; $h=g^α$ &amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;。&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;对 &amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;em&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;A&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;/em&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt; 的陷门查询，若关键词关联 &amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt; $g^β$ &amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;，则终止；否则返回合法陷门。&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;挑战阶段，随机选择 &amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt; $W^b$ &amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;​，构造密文 &amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt; $(g^γ,H_2​(e(g^β,g^{αγ})))$ &amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;。&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;攻击者成功时，&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;em&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;B&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;/em&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt; 从哈希列表提取&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt; $ e(g,g)^{αβγ}$ &amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;。&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;​&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;strong&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;优势分析&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;/strong&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;：&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;B&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;/em&gt;&amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt; 的成功概率为 &amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt; $  ϵ/(e \cdot q_T \cdot ​q_{H_2}​​ )  $ &amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;，其中 &amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt; $q_T$ &amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;​ 为陷门查询次数，&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt; $q_{H_2}$ &amp;lt;span style=&quot;color: rgba(255, 255, 255, 0.9)&quot;&amp;gt;&amp;lt;span style=&quot;background-color: rgb(33, 33, 33)&quot;&amp;gt;​​ 为哈希查询次数。&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Note(Manuscript) for &quot;Device-Enhanced Secure Cloud Storage with Keyword Searchable Encryption and Deduplication&quot;</title><link>https://blog.twlmgatito.com/posts/note-for-sas-ma-cloud-storage-system/</link><guid isPermaLink="true">https://blog.twlmgatito.com/posts/note-for-sas-ma-cloud-storage-system/</guid><pubDate>Sun, 23 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Paper reference:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://link.springer.com/chapter/10.1007/978-3-031-70903-6_20&quot;&gt;Device-Enhanced Secure Cloud Storage with Keyword Searchable Encryption and Deduplication | SpringerLink&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://link.springer.com/chapter/10.1007/11535218_19&quot;&gt;Secure Communications over Insecure Channels Based on Short Authenticated Strings | SpringerLink&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Bin-linear Map&lt;/h1&gt;
&lt;p&gt;&amp;lt;span class=&quot;highlight&quot; data-annotation=&quot;%7B%22attachmentURI%22%3A%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FA54BVCMA%22%2C%22pageLabel%22%3A%22400%22%2C%22position%22%3A%7B%22pageIndex%22%3A4%2C%22rects%22%3A%5B%5B39.40400871913795%2C212.336551831%2C385.19543709878843%2C222.428273756752%5D%2C%5B39.4025698368481%2C200.375553295%2C385.2086489790012%2C211.0848576768476%5D%2C%5B39.402826538581394%2C189.57869231754017%2C108.45759041264529%2C198.51514429687816%5D%5D%7D%2C%22citationItem%22%3A%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FJH5RQ343%22%5D%2C%22locator%22%3A%22400%22%7D%7D&quot; ztype=&quot;zhighlight&quot;&amp;gt;&amp;lt;a href=&quot;zotero://open/library/items/A54BVCMA?page=5&quot;&amp;gt;“Suppose that G is an additive group of prime order p and GT is a multiplicative group of the same order. A bilinear map e : G × G → GT has the following three properties”&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span class=&quot;citation&quot; data-citation=&quot;%7B%22citationItems%22%3A%5B%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FJH5RQ343%22%5D%2C%22locator%22%3A%22400%22%7D%5D%2C%22properties%22%3A%7B%7D%7D&quot; ztype=&quot;zcitation&quot;&amp;gt;(&amp;lt;span class=&quot;citation-item&quot;&amp;gt;&amp;lt;a href=&quot;zotero://select/library/items/JH5RQ343&quot;&amp;gt;Jiang et al., 2024, p. 400&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;)&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;p&gt;Suppose $e$ is a map, e(param[] elements) is a function call.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Bio-linearThis means that e respects the group operations in both groups, i.e., it is linear in both arguments. The exponents a and b act multiplicatively on the map.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$  e(Q,R) \neq 1, for; all; Q,R∈G, Q \neq R  $, where 1 denotes &lt;code&gt;UNIT Element&lt;/code&gt; in Multiplicative Group.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;e is effective to calcutale.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;MLE&lt;/h1&gt;
&lt;p&gt;&lt;s&gt;不用说懂得都懂&lt;/s&gt;&lt;/p&gt;
&lt;h1&gt;SAS-MA&lt;/h1&gt;
&lt;p&gt;Including 2 channels:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;open channel&amp;lt;span class=&quot;highlight&quot; data-annotation=&quot;%7B%22attachmentURI%22%3A%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FA54BVCMA%22%2C%22pageLabel%22%3A%22401%22%2C%22position%22%3A%7B%22pageIndex%22%3A5%2C%22rects%22%3A%5B%5B285.1118094399563%2C545.9691749927056%2C399.444596736801%2C554.9056269720436%5D%2C%5B53.57700598028832%2C534.0170440678318%2C399.4107235783859%2C542.9534960471698%5D%5D%7D%2C%22citationItem%22%3A%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FJH5RQ343%22%5D%2C%22locator%22%3A%22401%22%7D%7D&quot; ztype=&quot;zhighlight&quot;&amp;gt;&amp;lt;a href=&quot;zotero://open/library/items/A54BVCMA?page=6&quot;&amp;gt;“allows for transmission of messages of arbitrary length, but is subject to man-in-the-middle adversaries.”&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span class=&quot;citation&quot; data-citation=&quot;%7B%22citationItems%22%3A%5B%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FJH5RQ343%22%5D%2C%22locator%22%3A%22401%22%7D%5D%2C%22properties%22%3A%7B%7D%7D&quot; ztype=&quot;zcitation&quot;&amp;gt;(&amp;lt;span class=&quot;citation-item&quot;&amp;gt;&amp;lt;a href=&quot;zotero://select/library/items/JH5RQ343&quot;&amp;gt;Jiang et al., 2024, p. 401&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;)&amp;lt;/span&amp;gt; Allow any ${0,1}^*$ messages; affacted by Man-in-the-middle Attack.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SAS channel&amp;lt;span class=&quot;highlight&quot; data-annotation=&quot;%7B%22attachmentURI%22%3A%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FA54BVCMA%22%2C%22pageLabel%22%3A%22401%22%2C%22position%22%3A%7B%22pageIndex%22%3A5%2C%22rects%22%3A%5B%5B100.78179613468113%2C522.064913142958%2C361.072694015414%2C532.98743%5D%5D%7D%2C%22citationItem%22%3A%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FJH5RQ343%22%5D%2C%22locator%22%3A%22401%22%7D%7D&quot; ztype=&quot;zhighlight&quot;&amp;gt;&amp;lt;a href=&quot;zotero://open/library/items/A54BVCMA?page=6&quot;&amp;gt;“allows for transmission of up to t′-bit (e.g., 20-bit) messages”&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span class=&quot;citation&quot; data-citation=&quot;%7B%22citationItems%22%3A%5B%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FJH5RQ343%22%5D%2C%22locator%22%3A%22401%22%7D%5D%2C%22properties%22%3A%7B%7D%7D&quot; ztype=&quot;zcitation&quot;&amp;gt;(&amp;lt;span class=&quot;citation-item&quot;&amp;gt;&amp;lt;a href=&quot;zotero://select/library/items/JH5RQ343&quot;&amp;gt;Jiang et al., 2024, p. 401&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;)&amp;lt;/span&amp;gt; Allows for any ${0,1}^{t&apos;}$ messages.(t&apos; is a short interger, e.g. 20)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;“commitment“ is more alike a Hash sign of Message m for authentication.&lt;/p&gt;
&lt;h1&gt;Threat Model&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;CS \ KS:  compromised&amp;lt;span class=&quot;highlight&quot; data-annotation=&quot;%7B%22attachmentURI%22%3A%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FA54BVCMA%22%2C%22pageLabel%22%3A%22402%22%2C%22position%22%3A%7B%22pageIndex%22%3A6%2C%22rects%22%3A%5B%5B180.2653873864164%2C218.70994444120265%2C385.22794465299063%2C227.64639642054064%5D%2C%5B39.40319356190381%2C206.74884717655024%2C385.25783275250546%2C215.68529915588823%5D%2C%5B39.40319356190381%2C194.79671625167646%2C385.27178017554456%2C203.73316823101445%5D%2C%5B39.40319356190381%2C182.83561898702405%2C217.14593540814747%2C191.77207096636204%5D%5D%7D%2C%22citationItem%22%3A%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FJH5RQ343%22%5D%2C%22locator%22%3A%22402%22%7D%7D&quot; ztype=&quot;zhighlight&quot;&amp;gt;&amp;lt;a href=&quot;zotero://open/library/items/A54BVCMA?page=7&quot;&amp;gt;“An honest-but-curious cloud server may compromise the key servers to launch offline brute-force attacks and offline KGA against files and keywords, respectively. The number of compromised key servers is assumed to be less than the threshold.”&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span class=&quot;citation&quot; data-citation=&quot;%7B%22citationItems%22%3A%5B%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FJH5RQ343%22%5D%2C%22locator%22%3A%22402%22%7D%5D%2C%22properties%22%3A%7B%7D%7D&quot; ztype=&quot;zcitation&quot;&amp;gt;(&amp;lt;span class=&quot;citation-item&quot;&amp;gt;&amp;lt;a href=&quot;zotero://select/library/items/JH5RQ343&quot;&amp;gt;Jiang et al., 2024, p. 402&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;)&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open Channel:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;disclose pw &amp;amp; pw-derived sk&lt;/li&gt;
&lt;li&gt;pw is low-entropy&lt;/li&gt;
&lt;li&gt;A can reveal pw by existing &amp;lt;pw,sk&amp;gt; pairs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Trusted device owned by R&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;cln. &amp;lt;span class=&quot;highlight&quot; data-annotation=&quot;%7B%22attachmentURI%22%3A%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FA54BVCMA%22%2C%22pageLabel%22%3A%22403%22%2C%22position%22%3A%7B%22pageIndex%22%3A7%2C%22rects%22%3A%5B%5B211.08968652198027%2C436.32279462013236%2C399.4137058101841%2C445.2592465994704%5D%2C%5B66.5273830842246%2C424.37066369525854%2C362.8977894069446%2C433.30711567459656%5D%5D%7D%2C%22citationItem%22%3A%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FJH5RQ343%22%5D%2C%22locator%22%3A%22403%22%7D%7D&quot; ztype=&quot;zhighlight&quot;&amp;gt;&amp;lt;a href=&quot;zotero://open/library/items/A54BVCMA?page=8&quot;&amp;gt;“secure against an honest-but-curious cloud server, compromised key servers, and man-in-the-middle adversaries”&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span class=&quot;citation&quot; data-citation=&quot;%7B%22citationItems%22%3A%5B%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FJH5RQ343%22%5D%2C%22locator%22%3A%22403%22%7D%5D%2C%22properties%22%3A%7B%7D%7D&quot; ztype=&quot;zcitation&quot;&amp;gt;(&amp;lt;span class=&quot;citation-item&quot;&amp;gt;&amp;lt;a href=&quot;zotero://select/library/items/JH5RQ343&quot;&amp;gt;Jiang et al., 2024, p. 403&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;)&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;h1&gt;Construction&lt;/h1&gt;
&lt;h2&gt;phase 1.&lt;/h2&gt;
&lt;h3&gt;ParaGen:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;KS*n , 0&amp;lt;t&amp;lt;n : threshold&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;素数p阶加法群G，生成元P；p阶乘法群GT&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;e双线性映射：e: G × G -&amp;gt; GT&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Hash functions&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;H: *-&amp;gt;G&lt;/li&gt;
&lt;li&gt;h1: G&amp;amp;*-&amp;gt;K(secure para.)&lt;/li&gt;
&lt;li&gt;h2: GT-&amp;gt;K&lt;/li&gt;
&lt;li&gt;h3: *-&amp;gt;K&lt;/li&gt;
&lt;li&gt;h4: G&amp;amp;*-&amp;gt;Zp*&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对称加密 SEnc\SDec&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PKEnc\PKDec&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;ServerSecretGen:&lt;/h3&gt;
&lt;p&gt;KS 进行分布式密钥生成，产生α和β, and ones for their own.&lt;/p&gt;
&lt;p&gt;KS_i owns αi &amp;amp; βi.&lt;/p&gt;
&lt;p&gt;pk: V=α*P , Q=β*P and Vi Qi for KS_i of their own.&lt;/p&gt;
&lt;p&gt;&amp;lt;span class=&quot;highlight&quot; data-annotation=&quot;%7B%22attachmentURI%22%3A%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FA54BVCMA%22%2C%22pageLabel%22%3A%22403%22%2C%22position%22%3A%7B%22pageIndex%22%3A7%2C%22rects%22%3A%5B%5B276.7322854886617%2C158.5250944621%2C399.4017761988422%2C168.4876942161%5D%2C%5B53.57602892379428%2C146.9724637873616%2C399.3748729756876%2C155.90891576669958%5D%2C%5B53.57603217160175%2C133.86554353%2C399.391125895123%2C144.57494664150002%5D%2C%5B53.57731896793456%2C121.91364686200001%2C399.4062014756252%2C132.6229498155%5D%2C%5B53.579466303985384%2C109.952648327%2C325.05115699337745%2C120.6619522565%5D%5D%7D%2C%22citationItem%22%3A%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FJH5RQ343%22%5D%2C%22locator%22%3A%22403%22%7D%7D&quot; ztype=&quot;zhighlight&quot;&amp;gt;&amp;lt;a href=&quot;zotero://open/library/items/A54BVCMA?page=8&quot;&amp;gt;“the key servers perform the distributed secret generation algorithm illustrated in Algorithm 1 twice to create two secrets α and β that are shared among them. Each key server KSi (i ∈ [n]) owns the secret shares αi and βi. The public keys V = α · P , Q = β · P and the public shares Vi = αi · P , Qi = βi · P for i ∈ [n] are published.”&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span class=&quot;citation&quot; data-citation=&quot;%7B%22citationItems%22%3A%5B%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FJH5RQ343%22%5D%2C%22locator%22%3A%22403%22%7D%5D%2C%22properties%22%3A%7B%7D%7D&quot; ztype=&quot;zcitation&quot;&amp;gt;(&amp;lt;span class=&quot;citation-item&quot;&amp;gt;&amp;lt;a href=&quot;zotero://select/library/items/JH5RQ343&quot;&amp;gt;Jiang et al., 2024, p. 403&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;)&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;运行算法两次得到的α和β分别用于File M和Keyword kw的签名&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;Algorithm 1:&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;实质是分布式密钥协商算法：&lt;/p&gt;
&lt;p&gt;The public key PK is shared and available, while each server only knows its own private share.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;prepare(input): 安全参数K, 大素数p, KS index 1~n, threshold t.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;P是 Zp*的生成元&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;for every $KS_i$: , $$b_{i,0} \xleftarrow{\text$} Z^*&lt;em&gt;p$$    $f_i(x) = poly_x: b&lt;/em&gt;{i,(0~t)}$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;for every $KS_i$: 计算$b_{i,(0~t-1)} \cdot P$  并公开，send $f_i(j)$  to every $KS_j$ now, for all $KS_i$  owns all KS&apos;s $b_{(index),(range: t)} \cdot P$&amp;gt; $b_{i,i} \cdot P$ ：目的是盲化$b_i$&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;threshold参数在$poly_x$上体现，只需要t个KS协作即可完成认证&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;all trusted KS verifies: $  f_j(para: index)\cdot P == \sum_{n=0}^{t-1}{i^n \cdot b_{j,n}}  $&amp;gt; 确保得到的$b_i$与$KS_i$所声明的一致（在KS群中保持一致，不可能存在一个腐败的KS，除非所有KS都欺骗Server）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$KS_i$: 计算 $s_i = \sum_{q=1}^n{f_q(para:i)}$&lt;br /&gt;
$PK_i$ = $s_i \cdot P$ \\升阶(群内)盲化&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;for all KS: 协商获得 pk:  $PK=\sum_{q=1}^{n}{b_{q,0} \cdot P}$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;OUTPUT:&lt;/p&gt;
&lt;p&gt;PK | {$PK_i ; for; every; KS_i$}&lt;/p&gt;
&lt;p&gt;(${s_i}$ are stored inside KS as secure key)&lt;/p&gt;
&lt;p&gt;$s_i,; α_i,; β_i$&lt;/p&gt;
&lt;p&gt;use $PK_i (aka. ; V_i ; Q_i); to ; verify ; sign ; σ&apos;_i$&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;ReceiverKeyGen:&lt;/h3&gt;
&lt;p&gt;k∈Zp* randomly (在device D中储存)&lt;/p&gt;
&lt;p&gt;$$
\gamma = h_4(k \cdot H(pw) || pw ) ,  \Gamma = \gamma \cdot P
$$&lt;/p&gt;
&lt;p&gt;(&amp;lt;span class=&quot;highlight&quot; data-annotation=&quot;%7B%22attachmentURI%22%3A%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FA54BVCMA%22%2C%22pageLabel%22%3A%22404%22%2C%22position%22%3A%7B%22pageIndex%22%3A8%2C%22rects%22%3A%5B%5B67.99864627306988%2C371.180908692414%2C216.33279033116298%2C380.117360671752%5D%5D%7D%2C%22citationItem%22%3A%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FJH5RQ343%22%5D%2C%22locator%22%3A%22404%22%7D%7D&quot; ztype=&quot;zhighlight&quot;&amp;gt;&amp;lt;a href=&quot;zotero://open/library/items/A54BVCMA?page=9&quot;&amp;gt;“the password-derived public key Γ”&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt; )&lt;/p&gt;
&lt;p&gt;γ是私钥，Γ是公钥  used in PKEnc\PKDec&lt;/p&gt;
&lt;h2&gt;phase 2.&lt;/h2&gt;
&lt;h3&gt;MLEKey &amp;amp; sdk(server-derived key) Gen:&lt;/h3&gt;
&lt;p&gt;prepare: File M and its Keywords {w_j} T=|{wj}|&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;$$
r&apos; ; and ; {r_j} \xleftarrow{\text{$}} Z^*_p ;,; T=|{r_j}|
$$
$M’ = r’ \cdot H(M)$    文件HASH盲化&lt;br /&gt;
$w’_j=r_j \cdot H(w_j)$   Keyword hash盲化&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;共享 M’ 与  ${w’_j}$  with All KS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;for $KS_i$: Sign “the signatures  $σ&apos;&lt;em&gt;i = α_i \cdot M&apos; ; and ; δ&lt;/em&gt;{i&apos;,j} = β_i · w&apos;_j ; for ; j = 1, 2, · · · , T .$   These signatures will be transmitted to S”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Server verifies signs&lt;/p&gt;
&lt;p&gt;$$
e(σ’&lt;em&gt;i,P)==e(M’,Vi); and ; e(δ’&lt;/em&gt;{i,j},P) ==e(w’_j,Q_i)
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;p&gt;补充知识：&lt;/p&gt;
&lt;h3&gt;BLS签名的基本步骤&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;密钥生成&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;选择一个椭圆曲线群 G 和双线性映射e&lt;/p&gt;
&lt;p&gt;$$
e: G \times G \rightarrow G_T
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;生成私钥$x \in \mathbb{Z}_q$（一个随机数，q 是群的阶）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;计算公钥  $P = x \cdot G$ ，其中 G 是基点。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;签名生成&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;对消息 m 进行哈希处理，得到 H(m)，这里 H(m)是一个映射到椭圆曲线的点。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用私钥 x对 H(m) 进行签名：签名&lt;/p&gt;
&lt;p&gt;$$
\sigma = x \cdot H(m)
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;签名验证&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;验证者首先计算 H(m) 并得到消息的哈希值。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用公钥 P 和签名 σ 进行验证：检查是否满足以下等式：&lt;/p&gt;
&lt;p&gt;$$
e(\sigma, G) = e(H(m), P)
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果该等式成立，则签名是有效的，否则无效。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;正确性验证(双线性)&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;$$
\sigma = x \cdot H(m)&lt;br /&gt;
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;$$
P = x \cdot G&lt;br /&gt;
$$&lt;/p&gt;
&lt;p&gt;$$&lt;/p&gt;
&lt;p&gt;e(\sigma.G) = e(H(m),G)^x
$$&lt;/p&gt;
&lt;p&gt;$$
e(H(m),P) = e(H(m),G)^x
$$&lt;/p&gt;
&lt;h3&gt;拉格朗日插值（Lagrange Interpolation）&lt;/h3&gt;
&lt;p&gt;是一种多项式插值方法，用于通过已知的离散数据点（$x_i, y_i$​）构建一个多项式，该多项式通过这些数据点。具体来说，给定 n+1 个数据点 $(x_0, y_0), (x_1, y_1), \dots, (x_n, y_n)$，拉格朗日插值通过构造一个多项式 L(x)，使得：&lt;/p&gt;
&lt;p&gt;$$
L(x_i) = y_i, \quad \forall i = 0, 1, \dots, n
$$&lt;/p&gt;
&lt;p&gt;拉格朗日插值公式为：&lt;/p&gt;
&lt;p&gt;$$
L(x) = \sum_{i=0}^{n} y_i \cdot \ell_i(x)
$$&lt;/p&gt;
&lt;p&gt;其中，$\ell_i(x)$ 为第 i 个基拉格朗日多项式，其定义为：&lt;/p&gt;
&lt;p&gt;$$
\ell_i(x) = \prod_{\substack{0 \leq j \leq n \ j \neq i}} \frac{x - x_j}{x_i - x_j}
$$&lt;/p&gt;
&lt;p&gt;这意味着每个基多项式  $ell_i(x)$ 在 $x_i$ 处为1，其他数据点处为0。最终，L(x) 就是通过所有数据点的加权组合。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Encryption&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;使用MLE Key $ek_M$对称加密M：&lt;/p&gt;
&lt;p&gt;$$
C_M = SEnc(ek_M,M).
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Γ是用户的pw-derived pk，公钥加密MLE KEY:&lt;/p&gt;
&lt;p&gt;$$
C_{ek_M} = PKEnc(\Gamma,ek_M)
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;加密server-derived keywords , ξj 从Zp*中随机选取：&lt;/p&gt;
&lt;p&gt;$$
C_{sdk_wj} = (\xi_j \cdot P, h_2(\tau_j))
$$&lt;/p&gt;
&lt;p&gt;$$
where, \tau_j = e(H(sdk_{w_k},\xi_j \cdot \Gamma))
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;OUT SOURCE: $C_M$ , $C_(ek_M)$, ${C_(sdk_wj)}$ for j∈[T]&lt;/p&gt;
&lt;h3&gt;De-duplication&lt;/h3&gt;
&lt;p&gt;package:&lt;/p&gt;
&lt;p&gt;$$
L_R := (Ind_{M^&lt;em&gt;}, C_{eK_{M^&lt;/em&gt;}}, {C_{sdk_{w^*_j}}} )
$$&lt;/p&gt;
&lt;p&gt;$$
L_G ={ Ind_M*, X_M*, C_M*} ; where, X_M* = h_3(C_m*)
$$&lt;/p&gt;
&lt;h2&gt;phase 3. Data Access&lt;/h2&gt;
&lt;h3&gt;R: Key Recovery (SAS-MA)&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;$input: pw \quad output: private-key ; \gamma$&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Client  interact with Device:&lt;/p&gt;
&lt;p&gt;$$
r ∈ Z^*_p , ; z∈{0,1}^K, ; R_c ∈ {0,1}^{t&apos;}
$$&lt;/p&gt;
&lt;p&gt;$$
盲化 pw&apos;=r \cdot H(pw), ; Com=h_3(pw&apos; || R_c||z)
$$&lt;/p&gt;
&lt;p&gt;$$ pw&apos;; and  ;Com → D $$&lt;/p&gt;
&lt;p&gt;$$
D: R_D ∈ {0,1}^{t&apos;} → Client
$$&lt;/p&gt;
&lt;p&gt;$$
C: \Phi_C = R_C \oplus R_D ; send; (R_C,z) ;to ;D
$$&lt;/p&gt;
&lt;p&gt;在SAS信道中(C TO D)：&lt;/p&gt;
&lt;p&gt;$$
\Phi_C = R_C \oplus R_D
$$&lt;/p&gt;
&lt;p&gt;D 计算 checksum&lt;/p&gt;
&lt;p&gt;$$
\Phi_D =R_C \oplus R_D ; == \Phi_C
$$&lt;/p&gt;
&lt;p&gt;检查承诺&lt;/p&gt;
&lt;p&gt;$$
Com == h_3(pw&apos;||R_C||Z)
$$&lt;/p&gt;
&lt;p&gt;成功则进行(实质是一个同态加密，pw经过盲化，只传输并操作pw’，C拥有盲化因子, k是Device拥有的私钥)并 send to C:&lt;/p&gt;
&lt;p&gt;$$
v&apos;= k \cdot pw&apos;
$$&lt;/p&gt;
&lt;p&gt;C 解盲化并得到私钥γ&lt;/p&gt;
&lt;p&gt;$$
v=r^{-1} \cdot v&apos; ; and ; private-key ; \gamma = h_4(v||pw)
$$&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;即得到私钥 $\gamma = h_4(k \cdot H(pw) || pw )$&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Keyword Search&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;$$
input:; keyword ;w^*
$$&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Client interacts with KS to blinds w*, r*∈Zp*&lt;/p&gt;
&lt;p&gt;$$
W^* =  r^* \cdot H(w^*) ;→ ; KS
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For  $KS_i$ :&lt;/p&gt;
&lt;p&gt;$$
\xi&apos;_i = \beta_i \cdot W^* ; → C
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Client Check  * threshold by:&lt;/p&gt;
&lt;p&gt;$$
e(\xi&apos;_i,P) = e(W^*,Q_i)
$$&lt;/p&gt;
&lt;p&gt;then computes:&lt;/p&gt;
&lt;p&gt;$$
\xi = r^{*-1} \sum_{n∈L}\lambda_n \xi&apos;_n
$$&lt;/p&gt;
&lt;p&gt;computes server-derived kw:&lt;/p&gt;
&lt;p&gt;$$
sdk_{w*} = h_1(\xi||w^*)
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$T_{sdk_{w^&lt;em&gt;}} = \gamma \cdot H(sdk_{w^&lt;/em&gt;})$   send to CS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;query  $L_R$  for  $C_{sdk_w} =(A,B)$ &lt;br /&gt;
verify  $h_2(e(T_{sdk_{w^*}},A)) == B$, where A is randomly selected by CS &lt;br /&gt;
send (enc)MLE Key $C_{ek_M}$   (and  $C_M$  ?) to C&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Decryption&lt;/h3&gt;
&lt;p&gt;$ek_M = PKDec(\gamma,C_{ek_M}) → MLE ; Key$&lt;/p&gt;
&lt;p&gt;$ decrypts ; M=SDec(e_{k_M},C_M)$&lt;/p&gt;
&lt;h1&gt;Security Proof&lt;/h1&gt;
&lt;h2&gt;定理1. 如果使用盲BLS签名和SAS-MA，则DULCET是抗中间人攻击的&lt;/h2&gt;
&lt;p&gt;&amp;lt;span class=&quot;highlight&quot; data-annotation=&quot;%7B%22attachmentURI%22%3A%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FA54BVCMA%22%2C%22pageLabel%22%3A%22407%22%2C%22position%22%3A%7B%22pageIndex%22%3A11%2C%22rects%22%3A%5B%5B53.57699376294341%2C259.905737135855%2C399.433624012411%2C268.83222651543906%5D%2C%5B53.57699376294341%2C247.95360621098123%2C399.4027414712745%2C256.88009559056525%5D%2C%5B53.57699376294341%2C235.5740797566608%2C399.4024889076054%2C247.64235256299997%5D%2C%5B53.57774110636814%2C223.62231313662616%2C285.8597394107797%2C233.58491289062619%5D%5D%7D%2C%22citationItem%22%3A%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FJH5RQ343%22%5D%2C%22locator%22%3A%22407%22%7D%7D&quot; ztype=&quot;zhighlight&quot;&amp;gt;&amp;lt;a href=&quot;zotero://open/library/items/A54BVCMA?page=12&quot;&amp;gt;“Theorem 1. Assuming the blind BLS signature is of blindness and the short authentication string message authentication (SAS-MA) is secure, DULCET prevents a man-in-the-middle adversary A (e.g., CS∗) from learning the receiver R’s password-derived private key γ and password pw.”&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span class=&quot;citation&quot; data-citation=&quot;%7B%22citationItems%22%3A%5B%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FJH5RQ343%22%5D%2C%22locator%22%3A%22407%22%7D%5D%2C%22properties%22%3A%7B%7D%7D&quot; ztype=&quot;zcitation&quot;&amp;gt;(&amp;lt;span class=&quot;citation-item&quot;&amp;gt;&amp;lt;a href=&quot;zotero://select/library/items/JH5RQ343&quot;&amp;gt;Jiang et al., 2024, p. 407&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;)&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;p&gt;security provided by:&lt;/p&gt;
&lt;p&gt;$$
private-key: ; \gamma = h_4(v||pw)
$$&lt;/p&gt;
&lt;p&gt;$$
v= k \cdot H(pw)
$$&lt;/p&gt;
&lt;p&gt;$$
where ; k \xleftarrow{\text{$}} Z^*_p ; in ; trusted ; device
$$&lt;/p&gt;
&lt;p&gt;Blinded BLS signature &amp;amp; SAS-MA 保证k只在device中派生，不在任何信道中显式传输，对于一个外部敌手只能获得公钥并通过DGA来匹配pw：&lt;/p&gt;
&lt;p&gt;$$
public-key: ; \Gamma = \gamma \cdot P
$$
·&lt;/p&gt;
&lt;h2&gt;定理2. 对于所有PPT敌手，DULCET是不可预测的&lt;/h2&gt;
&lt;p&gt;&amp;lt;span class=&quot;highlight&quot; data-annotation=&quot;%7B%22attachmentURI%22%3A%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FA54BVCMA%22%2C%22pageLabel%22%3A%22408%22%2C%22position%22%3A%7B%22pageIndex%22%3A12%2C%22rects%22%3A%5B%5B39.40187341822877%2C425.9870362459722%2C385.23160427594865%2C435.9496359999722%5D%2C%5B39.4018715352974%2C414.0349053210984%2C337.14611965246513%2C423.9975050750984%5D%5D%7D%2C%22citationItem%22%3A%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FJH5RQ343%22%5D%2C%22locator%22%3A%22408%22%7D%7D&quot; ztype=&quot;zhighlight&quot;&amp;gt;&amp;lt;a href=&quot;zotero://open/library/items/A54BVCMA?page=13&quot;&amp;gt;“Theorem 2. DULCET is of unpredictability if for any PPT adversary A, the advantage of A winning the unpredictability experiment is negligible.”&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span class=&quot;citation&quot; data-citation=&quot;%7B%22citationItems%22%3A%5B%7B%22uris%22%3A%5B%22http%3A%2F%2Fzotero.org%2Fusers%2F16470860%2Fitems%2FJH5RQ343%22%5D%2C%22locator%22%3A%22408%22%7D%5D%2C%22properties%22%3A%7B%7D%7D&quot; ztype=&quot;zcitation&quot;&amp;gt;(&amp;lt;span class=&quot;citation-item&quot;&amp;gt;&amp;lt;a href=&quot;zotero://select/library/items/JH5RQ343&quot;&amp;gt;Jiang et al., 2024, p. 408&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;)&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;p&gt;在分布式密钥协商中，敌手最多能收集到t-1个子密钥(threshold t)&lt;/p&gt;
&lt;h3&gt;$ Exp^{UDP}_{A,DULCET}(1^{\lambda}) $&lt;/h3&gt;
&lt;p&gt;...&lt;/p&gt;
</content:encoded></item><item><title>C++ 使用MIDI库演奏《晴天》</title><link>https://blog.twlmgatito.com/posts/cpp-midi-sunnydays/</link><guid isPermaLink="true">https://blog.twlmgatito.com/posts/cpp-midi-sunnydays/</guid><description>谨以此篇献给我的一位好伙伴</description><pubDate>Thu, 13 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::note[题记]
那些在MIDI库里徘徊的十六分音符&lt;br /&gt;
终究没能拼成告白的主歌&lt;/p&gt;
&lt;p&gt;我把周杰伦的《晴天》写成C++的类&lt;br /&gt;
在每个midiEvent里埋藏故事的小黄花&lt;/p&gt;
&lt;p&gt;调试器的断点比初恋更漫长&lt;br /&gt;
而青春不过是一串未导出的cmake工程文件&lt;/p&gt;
&lt;p&gt;在堆栈溢出的夜晚&lt;br /&gt;
终将明白&lt;br /&gt;
有些旋律永远停在#pragma once的注释里&lt;br /&gt;
有些人永远停在未定义的引用里&lt;/p&gt;
&lt;p&gt;或许你我的心跳终归运行在不同的时钟频率&lt;br /&gt;
却愿始终记得如何编译出一场永不落幕的晴天&lt;br /&gt;
:::&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;TwilightLemon/SunnyDays&quot;}&lt;br /&gt;
就像在题记里说的一样，这是一个从未导出成功的工程文件。&lt;br /&gt;
所以如果你也想听听，可以在PowerShell里运行以下指令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/TwilightLemon/SunnyDays
cd SunnyDays
mkdir build
cd build
cmake .. -G &quot;MinGW Makefiles&quot;
mingw32-make
./SunnyDays.exe
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;s&gt;没环境？巧了，她也如是说。&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;下面来简单讲讲如何使用C++和MIDI库作曲吧。&lt;/p&gt;
&lt;h2&gt;一、开始工作&lt;/h2&gt;
&lt;h3&gt;引入MIDI库和相关控制类&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;在&lt;code&gt;CMakeLists.txt&lt;/code&gt;中：&lt;pre&gt;&lt;code&gt;target_link_libraries(SunnyDays winmm)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;在&lt;code&gt;MIDIHelper.h&lt;/code&gt;中：&lt;pre&gt;&lt;code&gt;#include &amp;lt;windows.h&amp;gt;
#pragma comment(lib,&quot;winmm.lib&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;定义Scale(音阶), Instrument(乐器, 仅包括部分)等枚举。我把Drum单独提了出来。&lt;pre&gt;&lt;code&gt;enum Scale
{
    X1 = 36, X2 = 38, X3 = 40, X4 = 41, X5 = 43, X6 = 45, X7 = 47,
    L1 = 48, L2 = 50, L3 = 52, L4 = 53, L5 = 55, L6 = 57, L7 = 59,
    M1 = 60, M2 = 62, M3 = 64, M4 = 65, M5 = 67, M6 = 69, M7 = 71,
    H1 = 72, H2 = 74, H3 = 76, H4 = 77, H5 = 79, H6 = 81, H7 = 83,
    LOW_SPEED = 500, MIDDLE_SPEED = 400, HIGH_SPEED = 300,
    _ = 0XFF
};
enum Drum{
    BassDrum = 36, SnareDrum = 38, ClosedHiHat = 42, OpenHiHat = 46
};
enum Instrument{
    AcousticGrandPiano = 0, BrightAcousticPiano = 1,
    ElectricGrandPiano = 2, HonkyTonkPiano = 3,
    ElectricPiano1 = 4, ElectricPiano2 = 5
};
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;一些基础方法，包括初始化/关闭设备、设置参数、播放单个音符和播放和弦等。&lt;pre&gt;&lt;code&gt;void initDevice();
void closeDevice();
void setInstrument(int channel, int instrument);
void setVolume(int channel, int volume);

void PlayNote(HMIDIOUT handle, UINT channel, UINT note, UINT velocity);

void playChord(HMIDIOUT handle, UINT channel, UINT note1, UINT note2, UINT note3, UINT note4, UINT velocity);

void playChord(HMIDIOUT handle, UINT channel, UINT note1, UINT note2, UINT note3, UINT velocity);
&lt;/code&gt;&lt;/pre&gt;
在&lt;code&gt;MIDIHelper.cpp&lt;/code&gt;中：&lt;pre&gt;&lt;code&gt;void initDevice(){
    midiOutOpen(&amp;amp;hMidiOut, 0, 0, 0, CALLBACK_NULL);
}

void closeDevice(){
    midiOutClose(hMidiOut);
}

void setInstrument(int channel,int instrument){
    if (channel &amp;gt; 15 || instrument &amp;gt; 127) return;
    DWORD message = 0xC0 | channel | (instrument &amp;lt;&amp;lt; 8);
    midiOutShortMsg(hMidiOut, message);
}

void setVolume(int channel,int volume){
    if (channel &amp;gt; 15 || volume &amp;gt; 127) return;
    DWORD message = 0xB0 | channel | (7 &amp;lt;&amp;lt; 8) | (volume &amp;lt;&amp;lt; 16);
    midiOutShortMsg(hMidiOut, message);
}

//播放单个音符，note是音符，velocity是力度
void PlayNote(HMIDIOUT handle, UINT channel, UINT note, UINT velocity) {
    if (channel &amp;gt; 15 || note &amp;gt; 127 || velocity &amp;gt; 127) return;
    DWORD message = 0x90 | channel | (note &amp;lt;&amp;lt; 8) | (velocity &amp;lt;&amp;lt; 16);
    midiOutShortMsg(handle, message);
}

//四指和弦
void playChord(HMIDIOUT handle, UINT channel, UINT note1, UINT note2, UINT note3, UINT note4, UINT velocity){
    if (channel &amp;gt; 15 || note1 &amp;gt; 127 || note2 &amp;gt; 127 || note3 &amp;gt; 127 || note4 &amp;gt; 127 || velocity &amp;gt; 127) return;
    DWORD message1 = 0x90 | channel | (note1 &amp;lt;&amp;lt; 8) | (velocity &amp;lt;&amp;lt; 16);
    DWORD message2 = 0x90 | channel | (note2 &amp;lt;&amp;lt; 8) | (velocity &amp;lt;&amp;lt; 16);
    DWORD message3 = 0x90 | channel | (note3 &amp;lt;&amp;lt; 8) | (velocity &amp;lt;&amp;lt; 16);
    DWORD message4 = 0x90 | channel | (note4 &amp;lt;&amp;lt; 8) | (velocity &amp;lt;&amp;lt; 16);
    midiOutShortMsg(handle, message1);
    midiOutShortMsg(handle, message2);
    midiOutShortMsg(handle, message3);
    midiOutShortMsg(handle, message4);
}

//三指和弦
void playChord(HMIDIOUT handle, UINT channel, UINT note1, UINT note2, UINT note3, UINT velocity) {
    if (channel &amp;gt; 15 || note1 &amp;gt; 127 || note2 &amp;gt; 127 || note3 &amp;gt; 127 || velocity &amp;gt; 127) return;
    DWORD message1 = 0x90 | channel | (note1 &amp;lt;&amp;lt; 8) | (velocity &amp;lt;&amp;lt; 16);
    DWORD message2 = 0x90 | channel | (note2 &amp;lt;&amp;lt; 8) | (velocity &amp;lt;&amp;lt; 16);
    DWORD message3 = 0x90 | channel | (note3 &amp;lt;&amp;lt; 8) | (velocity &amp;lt;&amp;lt; 16);
    midiOutShortMsg(handle, message1);
    midiOutShortMsg(handle, message2);
    midiOutShortMsg(handle, message3);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;初始化和结束&lt;/h3&gt;
&lt;p&gt;先在头文件中定义一个全局MIDI句柄:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;extern HMIDIOUT hMidiOut;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在入口处初始化MIDI设备并在结束时关闭：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HMIDIOUT hMidiOut;
int main() {
    initDevice();
    //...
    closeDevice();
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;初始化MIDI设备之后，为每一个乐器分配一个通道&lt;code&gt;channel&lt;/code&gt;（0~15，通常9分配给打击类乐器,例如鼓组），控制音量&lt;code&gt;volume&lt;/code&gt;，然后就可以开始演奏了。&lt;/p&gt;
&lt;h2&gt;二、自制简易乐谱&lt;/h2&gt;
&lt;p&gt;以&lt;code&gt;Voice.cpp&lt;/code&gt;为例,定义一个数组为频谱，控制停顿和音符，遍历数组播放：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;namespace SunnyDays{
    int channelVoice=1;
    void playVoice(int note, int velocity){
        PlayNote(hMidiOut, channelVoice, note, velocity);
    }
    void voice(){
        Sleep(13100);//等待前奏
        int sleep = 390;
        int data[] =
                {
                    //故事的小黄花
                    -90,
                    300,M5,M5,M1,M1,_,M2,M3,_,
                    //从出生那年就飘着
                    -90,
                    M5,M5,M1,M1,0,M2,M3,300,M2,M1,_,
                    //童年的荡秋千
                    -90,
                    300,M5,M5,M1,M1,_,M2,M3,_,
                    //随记忆一直晃到现在
                    -90,  
                    M3,_,500,M2,M3,M4,M3,M2,M4,M3,700,M2,700,_,
                    //...
                }
        for (auto i : data) {
            if(i==-30){logTime(&quot;Enter chorus&quot;);continue;}//调试用
            if(i==-90){NextLyric(); continue;}
            if (i == 0) { sleep = 180; continue; }
            //...
            if (i == _) {
                Sleep(390);
                continue;
            }

            playVoice(i, 80);
            Sleep(sleep);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;打个鼓：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;namespace SunnyDays{
    int channelBassDrum=9;

    void playDrum(int note, int velocity, int duration){
        PlayNote(hMidiOut, channelBassDrum, note, velocity);
        if(duration&amp;gt;0) {
            Sleep(duration);
            PlayNote(hMidiOut, channelBassDrum, note, 0);
        }
    }

    void bassDrum(){
        Sleep(11260);
        cout&amp;lt;&amp;lt;&quot;Drum Bass Start!&quot;&amp;lt;&amp;lt;endl;
        playDrum(SnareDrum,100,180);
        playDrum(SnareDrum,100,210);
        playDrum(BassDrum, 100, 210);
        playDrum(SnareDrum,100,190);
        playDrum(BassDrum, 100, 210);
        playDrum(SnareDrum,100,200);
        playDrum(SnareDrum,100,200);
        playDrum(OpenHiHat,100,-1);
        Sleep(200);
        //...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;简易副歌和弦，是从B站一位up主那里学的（已经忘记是哪位了qwq）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;namespace SunnyDays {
    int channelChord=2;
    void chordLevel(int level,int sleep,int repeat=2,int vel=70){
        repeat--;
        int down=8;
        if(level==1){
            //一级和弦 加右指
            playChord(hMidiOut, channelChord, M1, M3, M5, L1, vel);
            while(repeat--) {
                Sleep(sleep);
                playChord(hMidiOut, channelChord, M1, M3, M5, vel - down);
            }
        }else if(level==3){
            //三级和弦 加右指
            playChord(hMidiOut, channelChord, M3, M5, M7, L3, vel);
            while(repeat--) {
                Sleep(sleep);
                playChord(hMidiOut, channelChord, M3, M5, M7, vel - down);
            }
        }
        //...
    }
    void chord(){
        Sleep(63724);
        int sleep=740;
        int data[]={
                //刮风这天 我试过握着你手
                1,4,
                6,4,
                //但偏偏 雨渐渐
                4,2,
                5,2,
                //大到我看你不见
                1,4,
                //还有多久 我才能
                3,4,
                //↑ 在你身边
                6,4,
                //↓ 等到放晴的那天
                4,4,
                //↑ 也许我会比较好一点
                5,4,
                //..
        }
        int count=sizeof(data)/sizeof(int);
        for(int i=0;i&amp;lt;count;i+=2){
            cout&amp;lt;&amp;lt;&quot;chord &quot;&amp;lt;&amp;lt;data[i]&amp;lt;&amp;lt;&quot;  x&quot;&amp;lt;&amp;lt;data[i+1]&amp;lt;&amp;lt;endl;
            chordLevel(data[i],sleep,data[i+1]);
            Sleep(sleep);
        }
        //...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、合成演奏&lt;/h2&gt;
&lt;p&gt;我用了一个笨蛋方法，用多线程单独控制每一个通道，然后在主线程中调用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int main(){
    //...
    initDevice();
    //设置音量
    setVolume(channelChord,80);
    setVolume(channelMainLine,80);
    setVolume(channelVoice,120);
    setVolume(channelBassDrum,80);

    //设置乐器（特定音色）
    setInstrument(channelChord,ElectricPiano1);
    setInstrument(channelMainLine,ElectricPiano1);


    system(&quot;pause&quot;);//按下回车，就开始啦
    beginLogger();


    thread t0(voice);
    thread t1(mainLine);
    thread t2(bassDrum);
    thread t3(chord);
    t0.join();
    t1.join();
    t2.join();
    t3.join();

    closeDevice();
    //...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;（最后叠个甲，俺不懂音乐制作，更不会什么C++😿）&lt;/p&gt;
</content:encoded></item><item><title>无加密的机密性：Chaffing and Winnowing原理和C#实验仿真</title><link>https://blog.twlmgatito.com/posts/chaffing-and-winnowing/</link><guid isPermaLink="true">https://blog.twlmgatito.com/posts/chaffing-and-winnowing/</guid><description>本文介绍了 Rivest 提出的 Chaffing and Winnowing 技术，该技术通过在消息中混入无关信息 (chaff) 并添加认证码 (MAC) 来实现机密性，即使在拥有所有加密密钥的“独裁者”信道中进行通信也能保证消息安全。</description><pubDate>Sat, 23 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;p style=&quot;text-indent: 2em;&quot;&amp;gt;
最近在Crypto 2023上看到一篇有趣的文章&amp;lt;sup&amp;gt;[1]&amp;lt;/sup&amp;gt;,其旨在一个存在拥有所有密钥并知道所有消息的“独裁者”的信道中，通过安排与常规密文无法区分的隐藏的“变形”消息来进行机密通信的方法——变形签名，但由于本人技术水平有限无法完整实现整个系统。而当阅读到其中的一个技术分支——Chaffing and Winnowing时惊喜地发现其实现方法之巧妙，又恰好在图灵班的密码学课程中学到了相关的Diffie-Hellman密钥交换协议和消息验证码MAC等知识，于是选取这篇上世纪的论文&amp;lt;sup&amp;gt;[2]&amp;lt;/sup&amp;gt;来进行评论和仿真实验。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;p style=&quot;text-indent: 2em;&quot;&amp;gt;
（是的你没看错，这是我在学校写的某低水平评论论文，觉得方法比较新颖巧妙，于是分享出来）
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;p align=&quot;center&quot;&amp;gt;
摘要： 本文介绍了 Rivest 提出的 Chaffing and Winnowing 技术，该技术通过在消息中混入无关信息 (chaff) 并添加认证码 (MAC) 来实现机密性，即使在拥有所有加密密钥的“独裁者”信道中进行通信也能保证消息安全。文章详细阐述了该技术的原理、应用场景、潜在威胁以及未来研究方向，并通过实验仿真实现了整个技术流程。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h2&gt;一、简介&lt;/h2&gt;
&lt;p&gt;&amp;lt;p style=&quot;text-indent: 2em;&quot;&amp;gt;
Rivest为我们介绍了一种新技术Chaffing and Winnowing——原意是指从谷粒中分离谷壳的过程，它不进行传统意义上的加密，而是将消息（wheat）分块并作认证，混入无关信息（chaff）之后再进行通信。
&amp;lt;/p&amp;gt;
&amp;lt;p style=&quot;text-indent: 2em;&quot;&amp;gt;
该项技术可以说是变形签名&amp;lt;sup&amp;gt;[1]&amp;lt;/sup&amp;gt;的奠基之作，二者同样考虑一个问题：若独裁者拥有一个“后门”能够恢复密钥来对消息进行解密，那么如何在这样的信道上为消息提供机密性。Rivest说“像往常一样，关于规范技术的政策辩论最终会被技术创新所淘汰。试图通过规范加密来规范保密性，关闭了一扇门，却留下了另外两扇门（隐写术和Chaffing and Winnowing）.”后者与前者不同，隐写术&amp;lt;sup&amp;gt;[3]&amp;lt;/sup&amp;gt;注重于在较大的、看似普通的信息（如图片）中隐藏机密，使得在算法不公开的前提下，一个PPT敌手无法有效区分机密内容和普通内容。显然这样的机密性是由算法保密性提供，并不符合卡尔霍夫原则&amp;lt;sup&amp;gt;[4]&amp;lt;/sup&amp;gt;，即“一个密码系统的安全性不应依赖于算法的秘密性，而应依赖于密钥的秘密性”。
&amp;lt;/p&amp;gt;
&amp;lt;p style=&quot;text-indent: 2em;&quot;&amp;gt;
　　而在Chaffing and Winnowing中，消息机密性的保证被归因到MAC算法的认证性上，即在适应性选择明文攻击下具有存在不可伪造性&amp;lt;sup&amp;gt;[5]&amp;lt;/sup&amp;gt;。对手无法怀疑两种数据包的存在，不具有机密认证则亦无法区分它们，即使原消息不受任何加密。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h2&gt;二、技术概要&lt;/h2&gt;
&lt;p&gt;&amp;lt;p style=&quot;text-indent: 2em;&quot;&amp;gt;
原文中，作者循序渐进地介绍了这种技术的原理。
&amp;lt;/p&amp;gt;
&amp;lt;p style=&quot;text-indent: 2em;&quot;&amp;gt;
总体而言，发送方与接收方共享一个密钥，发送消息有两个部分：认证（添加消息认证码MAC）和添加chaff；接收方会通过验证MAC去除chaff（这个过程称为“winnowing”）以获得原始消息。整个过程中，没有对任何东西进行加密，因此可能不受出口管制（MAC不是加密）。
&amp;lt;/p&amp;gt;
&amp;lt;p style=&quot;text-indent: 2em;&quot;&amp;gt;
这是一个十分原始的想法，而后作者在考虑了实际运用的问题并做出诸多改进措施。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;消息包的拆分与组装中，定义一个消息包为包含序列号、消息和MAC的三元组，以便接收方除重、组装和识别丢失。并在这里提出一种优化：发送方按顺序发送包，接收方一旦验证成功该序列号，则丢弃后续所有相同序列号的包。&lt;/li&gt;
&lt;li&gt;一个良好的混淆过程会为消息使用的每一个序列号至少添加一个chaff。&lt;/li&gt;
&lt;li&gt;对手可能通过每个包裹的内容来区分chaff和wheat，无限拆分wheat只会让传输更加低效。
&amp;lt;p style=&quot;text-indent: 2em;&quot;&amp;gt;
为了解决以上问题，作者引用了自己的一项技术——全或无加密和包变换&amp;lt;sup&amp;gt;[11]&amp;lt;/sup&amp;gt;。简单来说，通过这种变换之后，只有接收者收到全部消息才可逆转变换得到原文，否则只能得到垃圾消息。（让我们把算法具体实现放在仿真实验的部分。）使用此变换后，再对消息进行分包签名和发送，能够减少敌手直接通过辨识消息来查找wheat组合的机会。
&amp;lt;/p&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三、研究分类、现状、难点分析与未来方向&lt;/h2&gt;
&lt;p&gt;&amp;lt;p style=&quot;text-indent: 2em;&quot;&amp;gt;
我们现在已经了解到Chaffing and Winnowing技术的原理，可以发现数据包被分为了两种——为己用和混淆视听。前者的用法似乎已经固定，后者则隐藏着更大的利用潜能与危机。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;1. 可否认加密&amp;lt;sup&amp;gt;[6]&amp;lt;/sup&amp;gt;&lt;/h4&gt;
&lt;p&gt;&amp;lt;p style=&quot;text-indent: 2em;&quot;&amp;gt;
如果对于每个wheat消息包，都生成一个使用特定密钥MAC认证的chaff，该chaff实际包含无害的消息，当使用该特定密钥进行认证时只能得到无害的消息，而真正在通讯双方交流的内容则被视为垃圾。如此使得在没有正确的解密密钥的情况下无法证明明文消息的来源或存在，即可否认加密。缺点在于，如果暴力机关要求通讯者提供所有密钥，则始终无法证明其是否已经提供全部密钥。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;2. 防止流量分析&lt;/h4&gt;
&lt;p&gt;&amp;lt;p style=&quot;text-indent: 2em;&quot;&amp;gt;
我尚未查找到已有的研究，只有维基百科中提及该技术的变体能够在分组网络中防止消息泄露和流量分析&amp;lt;sup&amp;gt;[7]&amp;lt;/sup&amp;gt;。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;3. 潜在的诬陷攻击&lt;/h4&gt;
&lt;p&gt;&amp;lt;p style=&quot;text-indent: 2em;&quot;&amp;gt;
试想通讯双方之间存在一个发起中间人攻击的主动敌手，能够用自己的密钥生成MAC并嵌入有害信息，这对于通讯双方接受消息没有影响。此时敌手拥有通讯的全文和自己的密钥，则他可以对通信双方进行诬陷：指定其通讯内容为有害信息而他们无法辩解。一方面，由于可否认加密的存在，他们可被认定为提供虚假无害的消息密钥；另一方面，通讯双方根本没有机密性需求，也没有使用Chaffing and Winnowing技术，他们本身就没有密钥，敌手嵌入的信息在通讯双方的可忽视区内。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;4. 如何协商一个私钥&lt;/h4&gt;
&lt;p&gt;&amp;lt;p style=&quot;text-indent: 2em;&quot;&amp;gt;
原文中仅用一个段落草草带过双方协商密钥的过程——“例如”使用Diffie-Hellman密钥交换协议&amp;lt;sup&amp;gt;[8]&amp;lt;/sup&amp;gt;,即通讯双方交换对方的公钥与自己的私钥计算得到共有的密钥。然而原始的协议仅在窃听敌手存在的情况下是安全的，通讯双方并不知道对方的身份，如果要抵御主动攻击敌手，则需要涉及数字证书和指定验证者签名&amp;lt;sup&amp;gt;[9]&amp;lt;/sup&amp;gt;等技术。这不在作者Rivest讨论的范围内，因为他的安全目标规定独裁者只知道加密密钥，而不限制认证。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;5. 消息体积剧增与对抗暴力枚举&lt;/h4&gt;
&lt;p&gt;&amp;lt;p style=&quot;text-indent: 2em;&quot;&amp;gt;
通讯双方仍需发送足够量的chaff包以迷惑敌手，使之在计算上找到包的组合不可行。这也无疑增大了包的体积，加之需要足够长度的MAC对抗碰撞。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;p&gt;目前该技术亟需解决的问题个人认为就是以上的3、4、5点；由此技术衍生的变形加密和变形签名相关研究已连续两年在CRYPTO发表&amp;lt;sup&amp;gt;[1][10]&amp;lt;/sup&amp;gt;。&lt;/p&gt;
&lt;h2&gt;四、实验仿真&lt;/h2&gt;
&lt;p&gt;以下使用C# .NET 9 on Windows平台进行实验，模拟通讯双方使用Chaffing and Winnowing技术的全部过程。&lt;/p&gt;
&lt;h4&gt;1. 通讯双方协商密钥&lt;/h4&gt;
&lt;p&gt;生成ECDiffieHellmanCng实体并生成密钥对，输出公钥，要求输入私钥后计算共同密钥：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Console.WriteLine(&quot;Step 1: Key Exchange&quot;);
Console.WriteLine(&quot;Generating key...&quot;);
using var client = new ECDiffieHellmanCng()
{
    KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash,
    HashAlgorithm = CngAlgorithm.Sha256
};
var publicKey = client.PublicKey.ToByteArray();
Console.WriteLine($&quot;Public Key: {Convert.ToBase64String(publicKey)}&quot;);
Console.WriteLine(&quot;Enter the public key of the other party:&quot;);
string otherKey = Console.ReadLine();
byte[] otherPublicKey = Convert.FromBase64String(otherKey);
var privateKey = client.DeriveKeyMaterial(CngKey.Import(otherPublicKey, CngKeyBlobFormat.EccPublicBlob));
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2. 实现一个AONT变换&lt;/h4&gt;
&lt;p&gt;这里采用原作者的简单变换：将数据按BLOCK_SIZE分块，生成与块等大的随机生成的密钥块key，将每个数据块与key逐比特异或得到结果，再将key与结果做异或储存在结果的最后一块之后：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static readonly int BLOCK_SIZE = 16;
    public static byte[] Transform(byte[] data){
        int blocks= (data.Length+BLOCK_SIZE-1)/BLOCK_SIZE;
        byte[] result = new byte[(blocks+1)*BLOCK_SIZE];//reserve one block for the hash
        byte[] key= new byte[BLOCK_SIZE];
        RandomNumberGenerator.Fill(key);

        Console.WriteLine($&quot;Key: {string.Join(&apos;,&apos;,key)}&quot;);

        for(int i=0;i&amp;lt;blocks;i++){
            int offset = i*BLOCK_SIZE;
            for(int j=0;j&amp;lt;BLOCK_SIZE&amp;amp;&amp;amp;offset+j&amp;lt;data.Length;j++){
                result[offset+j]=(byte)(data[offset+j]^key[j]);
            }
        }

        for(int i=0;i&amp;lt;blocks*BLOCK_SIZE;i++){
            key[i%BLOCK_SIZE]^=result[i];  //key XOR with data blocks
        }
        Array.Copy(key,0,result,blocks*BLOCK_SIZE,BLOCK_SIZE);
        
        Console.WriteLine($&quot;Last Block: {string.Join(&apos;,&apos;,key)}&quot;);

        return result;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;逆变换即先按BLOCK_SIZE分块，取出最后一块，依次与数据块做异或，解出key，再用key与数据块做异或还原原始数据。（注意，后续分包时，包的大小应该为BLOCK_SIZE的整数倍，以确保key按照相同的方式还原；逆变换时可能发现最后一块为{0}*，这并不影响解除key，因为与0异或为其本身。）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static bool Reverse(byte[] data,out byte[] result){
    if(data.Length%BLOCK_SIZE!=0){
        result=null;
        return false;
    }

    int oriBlocks = data.Length/BLOCK_SIZE-1;
    result= new byte[oriBlocks*BLOCK_SIZE];

    byte[] key= new byte[BLOCK_SIZE];
    Array.Copy(data,oriBlocks*BLOCK_SIZE,key,0,BLOCK_SIZE);
    Console.WriteLine($&quot;Last Block: {string.Join(&apos;,&apos;,key)}&quot;);
        
    for(int i=0;i&amp;lt;oriBlocks*BLOCK_SIZE;i++){
        key[i%BLOCK_SIZE]^=data[i];  //key XOR with data blocks
    }
    Console.WriteLine($&quot;Key: {string.Join(&apos;,&apos;,key)}&quot;);

    for(int i=0;i&amp;lt;oriBlocks;i++){
        int offset = i*BLOCK_SIZE;
        for(int j=0;j&amp;lt;BLOCK_SIZE&amp;amp;&amp;amp;offset+j&amp;lt;result.Length;j++){
            result[offset+j]=(byte)(data[offset+j]^key[j]);
        }
    }

    return true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上，如果不首先访问消息的每个块，就不可能恢复原始明文。&lt;/p&gt;
&lt;p&gt;有后人使用更复杂的算法实现AONT，例如使用线性变换而无任何加密假设的Stinson 算法&amp;lt;sup&amp;gt;[12]&amp;lt;/sup&amp;gt;。似乎能提供更高的安全性。&lt;/p&gt;
&lt;p&gt;调用ANOT变换，这里预先设定了发送的消息。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Console.WriteLine(&quot;Step 2: AONT&quot;);
string content = &quot;&quot;&quot;
    The power to authenticate is in many cases the power to control, 
    and handing all authentication power to the government is beyond all reason. 
                                                    -- Ronald L. Rivest, 1998
    &quot;&quot;&quot;;
byte[] dataBytes = Encoding.UTF8.GetBytes(content);
byte[] transformed = AONT.Transform(dataBytes);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3. 创建MAC消息验证码&lt;/h4&gt;
&lt;p&gt;这里使用HMAC-SHA256算法&amp;lt;sup&amp;gt;[13][14]&amp;lt;/sup&amp;gt;给出简单的MAC算法三元组，其中Gen已由最初的密钥交换提供。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    public static byte[] Sign(byte[] key,byte[] data){
        using var hmac = new HMACSHA256(key);
        return hmac.ComputeHash(data);
    }
    public static bool Verify(byte[] key,byte[] data,byte[] signature){
        using var hmac = new HMACSHA256(key);
        byte[] computed = hmac.ComputeHash(data);
        return computed.Length==signature.Length&amp;amp;&amp;amp;computed.AsSpan().SequenceEqual(signature);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4. 分包和签名&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;Console.WriteLine(&quot;Step 3: Packaging and Signing&quot;);
int numBlocks = (transformed.Length + BLOCK_SIZE - 1) / BLOCK_SIZE;
List&amp;lt;Package&amp;gt; packages = [];
for (int index = 0; index &amp;lt; numBlocks; index++)
{
    int offset = index * BLOCK_SIZE;
    int blockSize = Math.Min(BLOCK_SIZE, transformed.Length - offset);
    byte[] block = new byte[BLOCK_SIZE];
    Array.Copy(transformed, offset, block, 0, blockSize);

    byte[] signature = HMACHelper.Sign(privateKey, block);
    packages.Add(new Package(index, block, signature));
}
int packageCount = packages.Count;
Console.WriteLine($&quot;Wheat packages: {packageCount}&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;5. 加入带有随机数据的chaff&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;Console.WriteLine(&quot;Step 4: Adding Chaff Packages&quot;);
var rand = new Random();
for (int i = 0; i &amp;lt; packageCount; i++)
{
    int randCount = rand.Next(1, 5);
    for (int j = 0; j &amp;lt; randCount; j++)
    {
        var randData = new byte[BLOCK_SIZE];
        RandomNumberGenerator.Fill(randData);
        var randSignature = new byte[32];
        RandomNumberGenerator.Fill(randSignature);
        packages.Add(new Package(i, randData, randSignature));
    }
}
var sendPkg = packages.OrderBy(p =&amp;gt; p.index);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;6. 模拟发送&lt;/h4&gt;
&lt;p&gt;将所有的包按照index顺序排列后以Base64编码输出模拟发送，在接收端输入内容模拟接收。Base64编码能将byte[]转为文本格式，便于实验。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Console.WriteLine(&quot;Step 5: Sending Packages&quot;);
foreach (var pkg in sendPkg)
{
    Console.WriteLine($&quot;{pkg.index},{Convert.ToBase64String(pkg.data)},{Convert.ToBase64String(pkg.signature)}&quot;);
}
Console.ReadLine();
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;7. 模拟接收&lt;/h4&gt;
&lt;p&gt;要求输入所有的包，键空以结束。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Console.WriteLine(&quot;Enter packages, done with empty line:&quot;);
List&amp;lt;Package&amp;gt; received = [];
try
{
      while (true)
      {
           string line = Console.ReadLine();
            if (string.IsNullOrEmpty(line)) break;
            string[] parts = line.Split(&apos;,&apos;);
            int index = int.Parse(parts[0]);
            byte[] data = Convert.FromBase64String(parts[1]);
            byte[] signature = Convert.FromBase64String(parts[2]);
            received.Add(new Package(index, data, signature));
      }
        Console.WriteLine($&quot;Received packages: {received.Count}&quot;);
}
catch
{
     Console.WriteLine(&quot;Invalid input.&quot;);
     continue;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;8. Winnowing和逆变获得原消息&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;Console.WriteLine(&quot;Winnowing...&quot;);
        var wheat = received.Where(p =&amp;gt; HMACHelper.Verify(privateKey, p.data, p.signature)).OrderBy(p =&amp;gt; p.index).ToList();
        var wheatCount = wheat.Count;
        Console.WriteLine($&quot;Wheat packages: {wheatCount}&quot;);

        Console.WriteLine(&quot;Reverse AONT...&quot;);
        byte[] assembly = new byte[wheatCount * BLOCK_SIZE];
        for (int i = 0; i &amp;lt; wheatCount; i++)
        {
            Array.Copy(wheat[i].data, 0, assembly, i * BLOCK_SIZE, BLOCK_SIZE);
        }
        Console.WriteLine($&quot;Assembly: {Convert.ToBase64String(assembly)}&quot;);
        if (AONT.Reverse(assembly, out byte[] result))
        {
            Console.WriteLine($&quot;Result: {Encoding.UTF8.GetString(result)}&quot;);
        }
        else
        {
            Console.WriteLine(&quot;Reverse failed.&quot;);
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;9. 测试&lt;/h4&gt;
&lt;p&gt;两实例交换公钥并在内部生成私钥：
&lt;/p&gt;
&lt;p&gt;发送方生成消息包：
&lt;/p&gt;
&lt;p&gt;接收方收到不完整包流，逆变得到乱码；如果接受不完整块流，逆变将失败：
&lt;/p&gt;
&lt;p&gt;只有完整获得包流才能解出原始信息：
&lt;/p&gt;
&lt;p&gt;:::note[注释]&lt;br /&gt;
为了方便起见，这里没有处理原消息长度，padding产生的字节被编为乱码。&lt;br /&gt;
在实际运用中，包的结构并非像实验中一样直接暴露。&lt;br /&gt;
以上实验项目代码在Github上由本人公开。&lt;br /&gt;
:::
::github{repo=&quot;TwilightLemon/TestCWCrypto&quot;}&lt;/p&gt;
&lt;h2&gt;五、结论&lt;/h2&gt;
&lt;p&gt;Chaffing and Winnowing 技术提供了一种非传统的思路，利用 MAC 的认证性为消息提供机密性保障，即使在拥有所有加密密钥的信道中也能保证消息安全。该技术具有潜在的应用价值，但仍需解决一些挑战，例如协商私钥的安全性和消息体积的膨胀。未来研究可以探索更复杂的混淆过程、更高效的压缩算法以及更安全的密钥协商协议，以进一步提升 Chaffing and Winnowing 技术的安全性、效率和实用性。&lt;/p&gt;
&lt;h2&gt;参考文献&lt;/h2&gt;
&lt;p&gt;[1] Kutyłowski, M., Persiano, G., Phan, D.H., Yung, M., Zawada, M. (2023). “Anamorphic Signatures: Secrecy from a Dictator Who Only Permits Authentication!”. In: Handschuh, H., Lysyanskaya, A. (eds) Advances in Cryptology – CRYPTO 2023. Lecture Notes in Computer Science, vol 14082. Springer, Cham. https://doi.org/10.1007/978-3-031-38545-2_25&lt;/p&gt;
&lt;p&gt;[2] Ronald L. Rivest. “Chaffing and Winnowing: Confidentiality without Encryption”. Cryptobytes, Summer 1998. MIT Laboratory for Computer Science. Web. 23 Nov. 2024. https://people.csail.mit.edu/rivest/pubs/Riv98a.pdf&lt;/p&gt;
&lt;p&gt;[3] Peter Wayner. 1996. “Disappearing cryptography: being and nothingness on the net”. Academic Press Professional, Inc., USA. https://dl.acm.org/doi/10.5555/229879&lt;/p&gt;
&lt;p&gt;[4] Kerckhoffs, A. (1883). “La cryptographie militaire”. Journal des sciences militaires. https://www.petitcolas.net/kerckhoffs/crypto_militaire_1.pdf&lt;/p&gt;
&lt;p&gt;[5] Krawczyk, H., Bellare, M., and R. Canetti, “HMAC: Keyed-Hashing for Message Authentication”, RFC2104, February 1997.  https://www.rfc-editor.org/rfc/rfc2104&lt;/p&gt;
&lt;p&gt;[6] Canetti, Ran, Cynthia Dwork, Moni Naor, and Rafail Ostrovsky, “Deniable Encryption”, Proceedings CRYPTO ’97 (Springer 1997). https://link.springer.com/content/pdf/10.1007/BFb0052229.pdf&lt;/p&gt;
&lt;p&gt;[7] Chaffing and Winnowing – Variations WikiPedia. https://en.wikipedia.org/wiki/Chaffing_and_winnowing#Variations&lt;/p&gt;
&lt;p&gt;[8] Diffie, Whitfield; Hellman, Martin E. (November 1976). “New Directions in Cryptography”. IEEE Transactions on Information Theory. https://ee.stanford.edu/~hellman/publications/24.pdf&lt;/p&gt;
&lt;p&gt;[9] Jakobsson, Markus, Kazue Sako, and Russell Impagliazzo, “Designated Verifier Proofs and Their Applications’’, Pro-ceedings Eurocrypt ’ 96 (Springer 1996), 143—154. https://link.springer.com/content/pdf/10.1007/3-540-68339-9_13.pdf&lt;/p&gt;
&lt;p&gt;[10] Persiano, G., Phan, D.H., Yung, M. (2022). “Anamorphic Encryption: Private Communication Against a Dictator”. In: Dunkelman, O., Dziembowski, S. (eds) Advances in Cryptology – EUROCRYPT 2022. EUROCRYPT 2022. Lecture Notes in Computer Science, vol 13276. Springer, Cham. https://doi.org/10.1007/978-3-031-07085-3_2&lt;/p&gt;
&lt;p&gt;[11] Rivest, R. “All-Or-Nothing Encryption and the Package Transform”. Proceedings of the 1997 Fast Software Encryption Conference (Springer, 1997). https://people.csail.mit.edu/rivest/pubs/Riv97d.pdf&lt;/p&gt;
&lt;p&gt;[12] Stinson, D.R. “Something About All or Nothing (Transforms)”. Designs, Codes and Cryptography 22, 133–138 (2001). https://link.springer.com/article/10.1023/A:1008304703074&lt;/p&gt;
&lt;p&gt;[13] HMACSHA256 Class (System.Security.Cryptography) | Microsoft Learn. https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.hmacsha256&lt;/p&gt;
&lt;p&gt;[14] Preneel, B. (2024). “HMAC”. In: Jajodia, S., Samarati, P., Yung, M. (eds) Encyclopedia of Cryptography, Security and Privacy. Springer, Berlin, Heidelberg. https://doi.org/10.1007/978-3-642-27739-9_581-2&lt;/p&gt;
&lt;p&gt;[15] Siriwardena, P. (2020). “Base64 URL Encoding”. In: Advanced API Security. Apress, Berkeley, CA. https://doi.org/10.1007/978-1-4842-2050-4_20&lt;/p&gt;
</content:encoded></item><item><title>WPF中为Popup和ToolTip使用WindowMaterial特效 win10/win11</title><link>https://blog.twlmgatito.com/posts/wpf-use-windowmaterial-in-popup-and-tooltip/</link><guid isPermaLink="true">https://blog.twlmgatito.com/posts/wpf-use-windowmaterial-in-popup-and-tooltip/</guid><description>使用亚克力、云母材质和圆角</description><pubDate>Tue, 15 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;先看效果图：    win11:


win10:
&lt;/p&gt;
&lt;p&gt;大致思路是：通过反射获取&lt;code&gt;Popup&lt;/code&gt;内部的&lt;code&gt;原生窗口句柄&lt;/code&gt;，然后通过前文已经实现的&lt;code&gt;WindowMaterial&lt;/code&gt;类来应用窗口特效；对于&lt;code&gt;ToolTip&lt;/code&gt;，为了保持其易用性，我使用了&lt;code&gt;附加属性+全局样式&lt;/code&gt;的方式来实现，&lt;code&gt;ToolTip&lt;/code&gt;也是一个特殊的&lt;code&gt;Popup&lt;/code&gt;.&lt;br /&gt;
前文链接：&lt;a href=&quot;/posts/window-material-in-wpf&quot;&gt;WPF 模拟UWP原生窗口样式——亚克力|云母材质、自定义标题栏样式、原生DWM动画 （附我封装好的类）&lt;/a&gt;&lt;br /&gt;
本文的Demo：&lt;br /&gt;
::github{repo=&quot;TwilightLemon/WindowEffectTest&quot;}&lt;/p&gt;
&lt;h2&gt;一、获取原生窗口句柄&lt;/h2&gt;
&lt;p&gt;通过查阅.NET源码得知，Popup内部通过一个类型为&lt;code&gt;PopupSecurityHelper&lt;/code&gt;的私有字段&lt;code&gt;_secHelper&lt;/code&gt;来管理窗口&lt;code&gt;hWnd&lt;/code&gt;，并且在创建完成之时会触发&lt;code&gt;Popup.Opened&lt;/code&gt;事件。&lt;br /&gt;
通过反射来获取窗口句柄:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const BindingFlags privateInstanceFlag = BindingFlags.NonPublic | BindingFlags.Instance;
public static IntPtr GetNativeWindowHwnd(this Popup popup)
{
    //获取Popup内部的_secHelper字段Info
    var field = typeof(Popup).GetField(&quot;_secHelper&quot;, privateInstanceFlag);
    if (field != null)
    {
        //获取popup的_secHelper字段值
        if (field.GetValue(popup) is { } _secHelper)
        {
            //获取_secHelper的Handle属性Info
            if (_secHelper.GetType().GetProperty(&quot;Handle&quot;, privateInstanceFlag) is { } prop)
            {
                if (prop.GetValue(_secHelper) is IntPtr handle)
                {
                    //返回句柄
                    return handle;
                }
            }
        }
    }
    //未找到
    return IntPtr.Zero;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同样地，能在&lt;code&gt;ToolTip&lt;/code&gt;内部找到私有字段&lt;code&gt;_parentPopup&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static IntPtr GetNativeWindowHwnd(this ToolTip tip)
{
    var field=tip.GetType().GetField(&quot;_parentPopup&quot;, privateInstanceFlag);
    if (field != null)
    {
        if(field.GetValue(tip) is Popup{ } popup)
        {
            return popup.GetNativeWindowHwnd();
        }
    }
    return IntPtr.Zero;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、应用WindowMaterial特效&lt;/h2&gt;
&lt;p&gt;有了窗口句柄那么一切都好办了，直接调用我封装好的&lt;code&gt;WindowMaterial&lt;/code&gt;类，如果你想了解更多请查看前文。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static void SetPopupWindowMaterial(IntPtr hwnd,Color compositionColor,
     MaterialApis.WindowCorner corner= MaterialApis.WindowCorner.Round)
{
    if (hwnd != IntPtr.Zero)
    {
        int hexColor = compositionColor.ToHexColor();
        var hwndSource = HwndSource.FromHwnd(hwnd);
        //----
        MaterialApis.SetWindowProperties(hwndSource, 0);
        MaterialApis.SetWindowComposition(hwnd, true, hexColor);
        //----
        MaterialApis.SetWindowCorner(hwnd, corner);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据微软的设计规范，这里默认对普通Popup使用圆角，对ToolTip使用小圆角，使用亚克力材质并附加compositionColor。&lt;br /&gt;
在github中获取完整的WindowMaterial.cs，我可能会不定期地更新它：&lt;a href=&quot;https://github.com/TwilightLemon/WindowEffectTest/blob/master/WindowEffectTest/WindowMaterial.cs&quot;&gt;WindowEffectTest/WindowMaterial.cs at master · TwilightLemon/WindowEffectTest (github.com)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如果你想使用Mica或MicaAlt等材质则将上面框起来的代码替换为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MaterialApis.SetWindowProperties(hwndSource, -1);
MaterialApis.SetBackDropType(hwnd, MaterialType.Mica);
MaterialApis.SetDarkMode(hwnd, isDarkMode: true);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、没错我又封装了一个即开即用的类&lt;/h2&gt;
&lt;p&gt;在Demo中查看封装好的类：&lt;a href=&quot;https://github.com/TwilightLemon/WindowEffectTest/blob/master/WindowEffectTest/FluentPopup.cs&quot;&gt;WindowEffectTest/FluentPopup.cs at master · TwilightLemon/WindowEffectTest (github.com)&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;使用FluentPopup&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;local:FluentPopup x:Name=&quot;testPopup&quot;
                   StaysOpen=&quot;False&quot;
                   Placement=&quot;Mouse&quot;
                   Background=&quot;{DynamicResource PopupWindowBackground}&quot;&amp;gt;
    &amp;lt;Grid Height=&quot;120&quot; Width=&quot;180&quot;&amp;gt;
        &amp;lt;TextBlock HorizontalAlignment=&quot;Center&quot; VerticalAlignment=&quot;Center&quot;&amp;gt;
            nihao 
        &amp;lt;/TextBlock&amp;gt;
    &amp;lt;/Grid&amp;gt;
&amp;lt;/local:FluentPopup&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中，&lt;code&gt;Background&lt;/code&gt;属性是我自定义的一个依赖属性，只允许使用&lt;code&gt;SolidColorBrush&lt;/code&gt;，用于设置亚克力特效的CompositionColor。&lt;br /&gt;
如果你希望像Demo中那样让Popup失焦自动关闭，可以设置&lt;code&gt;StaysOpen&lt;/code&gt;为&lt;code&gt;False&lt;/code&gt;，在后台打开Popup时使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private async void ShowPopupBtn_Click(object sender, RoutedEventArgs e)
{
    await Task.Yield();
    testPopup.IsOpen = true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关于为什么要加上&lt;code&gt;await Task.Tield()&lt;/code&gt;，可以看看吕毅大佬的文章:&lt;a href=&quot;https://blog.walterlv.com/post/how-to-open-a-wpf-popup.html&quot;&gt;一点点从坑里爬出来：如何正确打开 WPF 里的 Popup？ - Walterlv&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;在全局内使用FluentToolTip&lt;/h4&gt;
&lt;p&gt;我自定义了一个附加属性&lt;code&gt;FluentTooltip.UseFluentStyle&lt;/code&gt;，你只需要在App.xaml中设置即可全局生效：
这里的&lt;code&gt;PopupWindowBackground&lt;/code&gt;和&lt;code&gt;ForeColor&lt;/code&gt;是我自定义的颜色资源，你可以根据自己的需要来设置。&lt;br /&gt;
同样地&lt;code&gt;Background&lt;/code&gt;仅支持&lt;code&gt;SolidColorBrush&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Style TargetType=&quot;{x:Type ToolTip}&quot;&amp;gt;
    &amp;lt;Setter Property=&quot;local:FluentTooltip.UseFluentStyle&quot;
            Value=&quot;True&quot; /&amp;gt;
    &amp;lt;Setter Property=&quot;Background&quot;
            Value=&quot;{DynamicResource PopupWindowBackground}&quot; /&amp;gt;
    &amp;lt;Setter Property=&quot;Foreground&quot;
            Value=&quot;{DynamicResource ForeColor}&quot; /&amp;gt;
&amp;lt;/Style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样你就可以方便地创建一个Fluent风格的ToolTip了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Button 
    ToolTip=&quot;xxxxx&quot;
    /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;参考链接&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://source.dot.net/#PresentationFramework/System/Windows/Controls/Primitives/Popup.cs,0dde044d24b22b3c&quot;&gt;Popup.cs at Dotnet Source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://source.dot.net/#PresentationFramework/System/Windows/Controls/ToolTip.cs,3920316e3b8e4b74&quot;&gt;ToolTip.cs at Dotnet Source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.walterlv.com/post/how-to-open-a-wpf-popup.html&quot;&gt;一点点从坑里爬出来：如何正确打开 WPF 里的 Popup？ - Walterlv&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;/posts/window-material-in-wpf&quot;&gt;WPF 模拟UWP原生窗口样式——亚克力|云母材质、自定义标题栏样式、原生DWM动画 （附我封装好的类） - TwilightLemon&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>WPF使用AppBar实现窗口停靠，适配缩放、全屏响应和多窗口并列（附封装好即开即用的附加属性）</title><link>https://blog.twlmgatito.com/posts/use-appbar-in-wpf/</link><guid isPermaLink="true">https://blog.twlmgatito.com/posts/use-appbar-in-wpf/</guid><pubDate>Sun, 29 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::note[AppBar]
在吕毅大佬的文章中已经详细介绍了什么是:AppBar: &lt;a href=&quot;https://blog.walterlv.com/post/dock-window-into-windows-desktop.html&quot;&gt;WPF 使用 AppBar 将窗口停靠在桌面上，让其他程序不占用此窗口的空间（附我封装的附加属性） - walterlv&lt;/a&gt;&lt;br /&gt;
即让窗口固定在屏幕某一边，并且保证其他窗口最大化后不会覆盖AppBar占据的区域（类似于Windows任务栏）。&lt;br /&gt;
:::&lt;/p&gt;
&lt;p&gt;但是在我的环境中测试时，上面的代码出现了一些问题，例如非100%缩放显示时的坐标计算异常、多窗口同时停靠时布局错乱等。所以我重写了AppBar在WPF上的实现，效果如图：&lt;br /&gt;

&lt;/p&gt;
&lt;h2&gt;一、AppBar的主要申请流程&lt;/h2&gt;
&lt;p&gt;主要流程如图：
&lt;/p&gt;
&lt;p&gt;（图注：ABN_POSCHANGED消息在任何需要调整位置之时都会触发，括号只是举个例子）&lt;/p&gt;
&lt;p&gt;核心代码其实在于如何计算停靠窗口的位置，要点是处理好一下几个方面：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;修改停靠位置时用原窗口的大小计算，被动告知需要调整位置时用即时大小计算&lt;/li&gt;
&lt;li&gt;像素单位与WPF单位之间的转换&lt;/li&gt;
&lt;li&gt;小心Windows的位置建议，并排停靠时会得到负值高宽，需要手动适配对齐方式&lt;/li&gt;
&lt;li&gt;有新的AppBar加入时，窗口会被系统强制移动到工作区(WorkArea)，这点我还没能找到解决方案，只能把移动窗口的命令通过Dispatcher延迟操作&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;二、如何使用&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;下载我封装好的库：&lt;a href=&quot;https://github.com/TwilightLemon/AppBarTest/blob/master/AppBarCreator.cs&quot;&gt;AppBarTest/AppBarCreator.cs at master · TwilightLemon/AppBarTest (github.com)&lt;/a&gt;
::github{repo=&quot;TwilightLemon/AppBarTest&quot;}&lt;/li&gt;
&lt;li&gt;在xaml中直接设置：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Window ...&amp;gt;

&amp;lt;local:AppBarCreator.AppBar&amp;gt;
    &amp;lt;local:AppBar x:Name=&quot;appBar&quot; Location=&quot;Top&quot; OnFullScreenStateChanged=&quot;AppBar_OnFullScreenStateChanged&quot;/&amp;gt;
&amp;lt;/local:AppBarCreator.AppBar&amp;gt;

...
&amp;lt;/Window&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者在后台创建：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private readonly AppBar appBar=new AppBar();

...Window_Loaded...
appBar.Location = AppBarLocation.Top;
appBar.OnFullScreenStateChanged += AppBar_OnFullScreenStateChanged;
AppBarCreator.SetAppBar(this, appBar);
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;另外你可能注意到了，这里有一个OnFullScreenStateChanged事件：该事件由AppBarMsg注册，在有窗口进入或退出全屏时触发，参数bool为true指示进入全屏。
你需要手动在事件中设置全屏模式下的行为，例如在全屏时隐藏AppBar&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;    private void AppBar_OnFullScreenStateChanged(object sender, bool e)
    {
        Debug.WriteLine(&quot;Full Screen State: &quot;+e);
        Visibility = e ? Visibility.Collapsed : Visibility.Visible;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我在官方的Flag上加了一个RegisterOnly，即只注册AppBarMsg而不真的停靠窗口，可以此用来作全屏模式监听。&lt;br /&gt;

4. 如果你需要在每个虚拟桌面都显示AppBar(像任务栏那样)，可以尝试为窗口使用SetWindowLong添加WS_EX_TOOLWINDOW标签（自行查找）&lt;/p&gt;
&lt;h2&gt;三、已知问题&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;在我的github上的实例程序中，如果你将两个同进程的窗口并排叠放的话，会导致explorer和你的进程双双爆栈，windows似乎不能很好地处理这两个并排放置的窗口，一直在左右调整位置，疯狂发送ABN_POSCHANGED消息。(快去clone试试，死机了不要打我) 但是并排放置示例窗口和OneNote的Dock窗口就没有问题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;计算停靠窗口时，如果选择停靠位置为Bottom，则系统建议的bottom位置值会比实际的高，测试发现是任务栏窗口占据了部分空间，应该是预留给平板模式的更大图标任务栏（猜测，很不合理的设计）

自动隐藏任务栏就没有这个问题：
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;没有实现自动隐藏AppBar，故没有处理与之相关的WM_ACTIVATE等消息，有需要的可以参考官方文档。（嘻 我懒）&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;参考文档：&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shappbarmessage&quot;&gt;SHAppBarMessage function (shellapi.h) - Win32 apps | Microsoft Learn&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/shell/abm-querypos&quot;&gt;ABM_QUERYPOS message (Shellapi.h) - Win32 apps | Microsoft Learn&lt;/a&gt; ABM_NEW &amp;amp; ABM_SETPOS etc..&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/zh-cn/windows/win32/shell/application-desktop-toolbars&quot;&gt;使用应用程序桌面工具栏 - Win32 apps | Microsoft Learn&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/jingzhongrong/article/details/5385951&quot;&gt;判断是否有全屏程序正在运行（C#）_c# 判断程序当前窗口是否全屏如果是返回原来-CSDN博客&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>WPF 模拟UWP原生窗口样式——亚克力|云母材质、自定义标题栏样式、原生DWM动画 （附我封装好的类）</title><link>https://blog.twlmgatito.com/posts/window-material-in-wpf/</link><guid isPermaLink="true">https://blog.twlmgatito.com/posts/window-material-in-wpf/</guid><description>材质、WindowChrome、原生DWM动画</description><pubDate>Thu, 22 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;先看一下最终效果，左图为使用亚克力材质并添加组合颜色的效果；右图为MicaAlt材质的效果。两者都自定义了标题栏并且最大限度地保留了DWM提供的原生窗口效果（最大化最小化、关闭出现的动画、窗口阴影、拖拽布局器等）。接下来把各部分的实现一个个拆开来讲讲。

&lt;/p&gt;
&lt;h1&gt;一、使用窗口材质特效&lt;/h1&gt;
&lt;p&gt;先粗略介绍一下目前win11和win10上的材质特效类型及一些特性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[Windows10 1903 ~ Windows11 lastest] ：Acrylic 亚克力材质 |  支持使用组合颜色 | 窗口失去焦点时不失效、有拖动窗口延迟 | API: &lt;code&gt;SetWindowCompositionAttribute&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;1803版本开放此API 但是对Win32应用支持不好&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Windows11:   API: &lt;code&gt;SetWindowAttribute&lt;/code&gt; 其实现的材质特性：原生不支持组合颜色 对于非WindowStyle.None的窗口失焦失效 无拖动窗口延迟 提供了暗亮模式&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Acrylic&lt;/code&gt; 亚克力材质  动态模糊，背景取决于窗口下方的内容&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Mica&lt;/code&gt; 云母材质 背景仅取桌面壁纸(第三方动态壁纸软件无效)   颜色变化较为平缓   win11系统应用的窗口背景大部分使用此材质&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MicaAlt&lt;/code&gt; 同Mica材质，区别是此材质的颜色变化更突出   文件管理器的标题栏使用此材质&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;在win11上同样支持使用win10的SetWindowCompositionAttribute启用旧版API，只不过需要不同的使用条件&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;前面的文章中介绍了在win11上启用材质的方法，但有不少弊端。之后诺尔大佬探究出从底层满足调用条件的方法，总结如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;无论调用哪个API，都需要设置&lt;code&gt;AllowTransparency=&quot;True&quot;&lt;/code&gt;，弊端是带来性能问题和鼠标穿透(即使DWM已经绘制了底层颜色)，改为调用：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;var hwndSource = (HwndSource)PresentationSource.FromVisual(_window);
hwndSource.CompositionTarget.BackgroundColor = Colors.Transparent;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;WindowChrome.GlassFrameThickness&lt;/code&gt; 对于&lt;code&gt;SetWindowCompositionAttribute&lt;/code&gt;需要值为&lt;code&gt;-1&lt;/code&gt;，另一者则需要为&lt;code&gt;0&lt;/code&gt;，弊端是可能我们并不需要WindowsChrome来拓展整个客户区，改为调用&lt;code&gt;DwmExtendFrameIntoClientArea&lt;/code&gt;，详细见后文示例程序。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果你想动手实现一下，可以参照：&lt;a href=&quot;https://slimenull.com/posts/20240530104846/&quot;&gt;[.NET,WPF] 窗体云母, 亚克力, 透明, 混合颜色, 模糊背景, 亮暗色主题全讲 (slimenull.com)&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;下面给出我封装好的附加属性：&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/TwilightLemon/WindowEffectTest/blob/master/WindowEffectTest/WindowMaterial.cs&quot;&gt;WindowEffectTest/WindowEffectTest/WindowMaterial.cs at master · TwilightLemon/WindowEffectTest (github.com)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;使用方法很简单，在你的xaml中添加以下作为Window的子标签，并且将Window.Background设为Transparent.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;local:WindowMaterial.Material&amp;gt;
        &amp;lt;local:WindowMaterial x:Name=&quot;windowMaterial&quot; 
                              IsDarkMode=&quot;True&quot; 
                              UseWindowComposition=&quot;False&quot; 
                              MaterialMode=&quot;MicaAlt&quot; 
                              CompositonColor=&quot;#CC6699FF&quot; &amp;gt;
        &amp;lt;/local:WindowMaterial&amp;gt;
&amp;lt;/local:WindowMaterial.Material&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;属性解释：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;IsDarkMode&lt;/code&gt;: 暗色模式，主要对&lt;code&gt;Mica(Alt)&lt;/code&gt;材质生效&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UseWindowComposition&lt;/code&gt;: 在win10上无效，指示使用&lt;code&gt;SetWindowCompositionAttribute&lt;/code&gt;，在win11上启用旧版的亚克力特效，一般用于创建无边框窗口的背景材质，此属性为&lt;code&gt;True&lt;/code&gt;时会忽略&lt;code&gt;MaterialMode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MaterialMode&lt;/code&gt;: 指示使用的材质类型 &lt;code&gt;None&lt;/code&gt; / &lt;code&gt;Acrylic&lt;/code&gt; / &lt;code&gt;Mica&lt;/code&gt; / &lt;code&gt;MicaAlt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CompositionColor&lt;/code&gt;: 指示使用混合颜色的值，仅对&lt;code&gt;MaterialMode&lt;/code&gt;=&lt;code&gt;Acrylic&lt;/code&gt;(直接设置&lt;code&gt;Window.Background&lt;/code&gt;) 和 使用&lt;code&gt;SetWindowCompositionAttribute&lt;/code&gt;时有效&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;幸运的话可以得到以下效果：

如果尝试使用亚克力材质并设置混合色的话，无论使用哪个API都会得到类似的效果：

区别在于：如果使用SetWindowAttribute提供的亚克力材质，可以调整IsDarkMode来配置背景色，但是效果不是很好。
&lt;/p&gt;
&lt;h2&gt;使用附加的WindowChromeEx来将客户区拓展至标题栏&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;如果&lt;code&gt;WindowChrome&lt;/code&gt;直接附加在窗口上会覆盖掉我们设置的&lt;code&gt;GlassFrameThickness&lt;/code&gt;，故这里的设计是将&lt;code&gt;WindowChrome&lt;/code&gt;附加在&lt;code&gt;WindowMaterial&lt;/code&gt;上进行管理。你可以接着使用熟悉的&lt;code&gt;WindowChrome&lt;/code&gt;提供的属性，然后把它作为资源提供给&lt;code&gt;WindowMaterial.WindowChromeEx&lt;/code&gt;属性。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Window.Resources&amp;gt;
        &amp;lt;WindowChrome x:Key=&quot;windowChrome&quot; ResizeBorderThickness=&quot;8&quot;/&amp;gt;
    &amp;lt;/Window.Resources&amp;gt;
    
&amp;lt;local:WindowMaterial.Material&amp;gt;
    &amp;lt;local:WindowMaterial x:Name=&quot;windowMaterial&quot; 
                          IsDarkMode=&quot;True&quot; 
                          UseWindowComposition=&quot;False&quot;
                          WindowChromeEx=&quot;{StaticResource windowChrome}&quot;
                          MaterialMode=&quot;Acrylic&quot; 
                          CompositonColor=&quot;#CC6699FF&quot; &amp;gt;
    &amp;lt;/local:WindowMaterial&amp;gt;
&amp;lt;/local:WindowMaterial.Material&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后就能得到：

MicaAlt (DarkMode) 以及 使用 WindowComposition 的亚克力材质效果：
&lt;/p&gt;
&lt;h1&gt;二、自定义标题栏并保留DWM动画&lt;/h1&gt;
&lt;p&gt;简单地介绍以下我的实现： 在Windows所以窗口创建底层都是走的WinAPI，WPF也不例外。可以通过仅提供&lt;code&gt;WS_CAPTION&lt;/code&gt;标签来创建一个没有三大按钮的标题栏：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[DllImport(&quot;user32.dll&quot;, EntryPoint = &quot;SetWindowLong&quot;)]
private static extern int SetWindowLong32(HandleRef hWnd, int nIndex, int dwNewLong);

[DllImport(&quot;user32.dll&quot;, EntryPoint = &quot;SetWindowLongPtr&quot;)]
private static extern IntPtr SetWindowLongPtr64(HandleRef hWnd, int nIndex, IntPtr dwNewLong);

public const int GWL_STYLE = -16;
public const long WS_CAPTION = 0x00C00000L;

public static IntPtr SetWindowLongPtr(HandleRef hWnd, int nIndex, IntPtr dwNewLong)
            =&amp;gt; IntPtr.Size == 8
            ? SetWindowLongPtr64(hWnd, nIndex, dwNewLong)
            : new IntPtr(SetWindowLong32(hWnd, nIndex, dwNewLong.ToInt32()));

public static void EnableDwmAnimation(Window w)
{
    var myHWND = new WindowInteropHelper(w).Handle;
    IntPtr myStyle = new(WS_CAPTION);
    SetWindowLongPtr(new HandleRef(null, myHWND), GWL_STYLE, myStyle);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
然后按照上述的方法添加WindowChrome让客户区覆盖标题栏即可。 （这里提前绘制好了自定义的标题栏样式，你需要自行处理暗色模式的变化等等）
&lt;/p&gt;
&lt;p&gt;这样&lt;code&gt;WindowStyle&lt;/code&gt;就会失效，并且在实现&lt;code&gt;WindowStyle.None&lt;/code&gt;的效果同时带上&lt;code&gt;WS_CAPTION&lt;/code&gt;标签让DWM认为这是一个“原生”标题栏窗口。经过测试，另外还需加上&lt;code&gt;WS_THICKFRAME|WS_MAXIMIZEBOX|WS_MINIMIZEBOX&lt;/code&gt; 等标签让窗口行为更贴近原生。（添加&lt;code&gt;WS_THICKFRAME&lt;/code&gt;在移动窗口时出现系统的窗口布局器）&lt;/p&gt;
&lt;p&gt;同样失效的还有&lt;code&gt;ResizeMode.NoResize&lt;/code&gt;，如果你需要固定窗口大小，目前暂行的方法是设置&lt;code&gt;WindowsChrome&lt;/code&gt;的&lt;code&gt;ResizeBorderThickness=&quot;0&quot;&lt;/code&gt;，如果按照WinAPI的方法加上WS_SYSMENU则会同时带回原生标题栏的三大按钮。（为什么微软对它的解释是标题栏菜单..?）&lt;/p&gt;
&lt;h3&gt;使用我封装好的附加属性：&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/TwilightLemon/WindowEffectTest/blob/master/WindowEffectTest/DwmAnimation.cs&quot;&gt;WindowEffectTest/WindowEffectTest/DwmAnimation.cs at master · TwilightLemon/WindowEffectTest (github.com)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;在Window标签中添加：（同样地这会使&lt;code&gt;WindowStyle&lt;/code&gt;和&lt;code&gt;ResizeMode&lt;/code&gt;失效）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;local:DwmAnimation.EnableDwmAnimation=&quot;True&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;三、添加更多窗口行为&lt;/h1&gt;
&lt;p&gt;以下Demo使用了诺尔大佬的WPF Suite来帮助简化流程。&lt;/p&gt;
&lt;h3&gt;Demo 1. 创建一个失焦不失效的亚克力材质圆角窗口 (Win11)，所有设置如下：&lt;/h3&gt;
&lt;p&gt;
&lt;/p&gt;
&lt;p&gt;关键部分在&lt;code&gt;ws:WindowOption.Corner=&quot;Round&quot;&lt;/code&gt;这一句，始终使用圆角窗口，并且拥有原生边框阴影 （右图为对照，无阴影）&lt;/p&gt;
&lt;p&gt;手动实现参照官方文档：&lt;a href=&quot;https://learn.microsoft.com/zh-cn/windows/apps/desktop/modernize/ui/apply-rounded-corners&quot;&gt;Apply rounded corners in desktop apps - Windows apps | Microsoft Learn&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Demo 2. 添加win11的布局器&lt;/h3&gt;
&lt;p&gt;在Button中添加 &lt;code&gt;ws:WindowOption.IsMaximumButton=&quot;True&quot;&lt;/code&gt; 以在鼠标悬停时显示布局器&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;本文的所有效果均可通过诺尔大佬的WPF Suite快速实现，大家快去用呀！&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://wpfsuite.elecho.dev/&quot;&gt;Documentation | EleCho.WpfSuite&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;最后附上测试项目地址：&lt;/h3&gt;
&lt;p&gt;::github{repo=&quot;TwilightLemon/WindowEffectTest&quot;}&lt;/p&gt;
&lt;p&gt;参考文章：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/zh-cn/windows/apps/design/signature-experiences/materials&quot;&gt;在 Windows 11 应用中使用的材料 - Windows apps | Microsoft Learn&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://slimenull.com/posts/20240530104846/&quot;&gt;[.NET,WPF] 窗体云母, 亚克力, 透明, 混合颜色, 模糊背景, 亮暗色主题全讲 (slimenull.com)&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://www.cnblogs.com/TwilightLemon/p/17479921.html&quot;&gt;WPF在win10/11上启用模糊特效 适配Dark/Light Mode - TwilightLemon - 博客园 (cnblogs.com)&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;......&lt;/p&gt;
</content:encoded></item><item><title>.NET App 与Windows系统媒体控制(SMTC)交互</title><link>https://blog.twlmgatito.com/posts/dotnet-windows-smtc/</link><guid isPermaLink="true">https://blog.twlmgatito.com/posts/dotnet-windows-smtc/</guid><description>什么？！这个API并没有对win32应用开放？！</description><pubDate>Tue, 02 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;当你使用Edge等浏览器或系统软件播放媒体时，Windows控制中心就会出现相应的媒体信息以及控制播放的功能，如图。&lt;br /&gt;

:::note[WHAT IS SMTC]
&lt;code&gt;SMTC (SystemMediaTransportControls)&lt;/code&gt; 是一个Windows App SDK (旧为UWP) 中提供的一个API，用于与系统媒体交互。接入SMTC的好处在于，将媒体控制和媒体信息共享给系统，使用通用的特性（例如接受键盘快捷键的播放暂停、接受蓝牙设备的控制），便于与其它支持SMTC的应用交互等。
:::
在UWP App中使用它很简单，只需要调用&lt;code&gt;SystemMediaTransportControls.GetForCurrentView()&lt;/code&gt;方法即可，但是该方法仅限在有效的UWP App中调用，否则将抛出&lt;code&gt;“Invalid window handle”&lt;/code&gt;异常。实际上，在官方文档中提到所有&lt;code&gt;XXXForCurrentView&lt;/code&gt;方法均不适用于UWP App以外的程序调用。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这些&lt;code&gt;XxxForCurrentView&lt;/code&gt;方法对&lt;code&gt;ApplicationView&lt;/code&gt;类型具有隐式依赖关系，桌面应用不支持该类型。由于桌面应用不支持 &lt;code&gt;ApplicationView&lt;/code&gt;，因此也不支持任何&lt;code&gt;XxxForCurrentView&lt;/code&gt;方法。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;此外官方文档还给出一个可替代的接口&lt;code&gt;ISystemMediaTransportControlsInterop&lt;/code&gt;，然而这个接口在给的SDK中有保护性，无法访问。&lt;/p&gt;
&lt;p&gt;至此，直接创建SMTC的方法走不通。但是我发现一个奇怪的地方，UWP提供的在&lt;code&gt;Windows.Media.Playback&lt;/code&gt;命名空间下的&lt;code&gt;MediaPlayer&lt;/code&gt;可以和SMTC自动集成，并且可以通过&lt;code&gt;SystemMediaTransportControls&lt;/code&gt;属性直接拿到SMTC对象。MediaPlayer内部通过某种COM组件直接创建了该NativeObject，而没有走API提供的&lt;code&gt;GetForCurrentView&lt;/code&gt;或&lt;code&gt;FromAbi&lt;/code&gt;方法。也就是说，SMTC组件其实不需要使用合法的UWP Window句柄来创建，只不过可能为了某些特性而加上了该限制(后文将提到)。幸运的是，&lt;code&gt;MediaPlayer&lt;/code&gt;帮我们绕过了这点。&lt;/p&gt;
&lt;p&gt;下文讲解手动与SMTC交互而不是直接使用MediaPlayer进行播放，你的项目可能已经有了其它的解码器(如WPF版本的MediaPlayer、Bass.Net解码器、NAudio等)，则只需要将交互部分接入SMTC而不更换解码器。&lt;/p&gt;
&lt;p&gt;文末提供了我封装好的&lt;code&gt;SMTCCreator&lt;/code&gt;和&lt;code&gt;SMTCListener&lt;/code&gt;，可以直接使用。&lt;/p&gt;
&lt;h2&gt;一、引用WinRT API到项目&lt;/h2&gt;
&lt;p&gt;最便捷的方法是直接修改目标框架到win10，这样就能自动引入WinRT API:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;TargetFramework&amp;gt;net8.0-windows10.0.19041.0&amp;lt;/TargetFramework&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者一些其他的方法，可以参考这篇博客：&lt;a href=&quot;https://www.cnblogs.com/zhaotianff/p/17320807.html&quot;&gt;如何在WPF中调用Windows 10/11 API(UWP/WinRT) - zhaotianff - 博客园 (cnblogs.com)&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;二、通过MediaPlayer获取SMTC对象&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;using Windows.Media;
using Windows.Storage.Streams;
...
private readonly Windows.Media.Playback.MediaPlayer _player = new();
private readonly SystemMediaTransportControls _smtc;
...
//先禁用系统播放器的命令
_player.CommandManager.IsEnabled = false;
//直接创建SystemMediaTransportControls对象被平台限制，神奇的是MediaPlayer对象可以创建该NativeObject
_smtc = _player.SystemMediaTransportControls;
//启用smtc以进行自定义
_smtc.IsEnabled = true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拿到SMTC对象之后的操作与UWP中无异，这里简单看一下：&lt;/p&gt;
&lt;h3&gt;1.设置可交互性&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;_smtc.IsPlayEnabled = true;
_smtc.IsPauseEnabled = true;
_smtc.IsNextEnabled = true;
_smtc.IsPreviousEnabled = true;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.设置媒体信息&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;var updater = _smtc.DisplayUpdater;
updater.AppMediaId = &quot;xxx&quot;; //用于区分不同来源的媒体
updater.Type = MediaPlaybackType.Music; //必须指定媒体类型否则抛异常
updater.MusicProperties.Title = “Title”；//标题
/*...省略相同的字段设置...*/
updater.Thumbnail = RandomAccessStreamReference.CreateFromUri(new Uri(ImgUrl));//设置封面图
updater.Update();//最后调用以生效
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;播放状态需要单独设置:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;_smtc.PlaybackStatus = MediaPlaybackStatus.Playing; //Paused \ Stopped
//直接设置无需更新
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.响应SMTC交互&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;_smtc.ButtonPressed += _smtc_ButtonPressed;
...
 private void _smtc_ButtonPressed(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
        {
            switch(args.Button)
            {
                case SystemMediaTransportControlsButton.Play:
                    //Play
                    break;
                case SystemMediaTransportControlsButton.Pause:
                    //Pause
                    break;
                case SystemMediaTransportControlsButton.Next:
                    //Next
                    break;
                case SystemMediaTransportControlsButton.Previous:
                    //Previous
                    break;
            }
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意，文中所有SMTC的事件均由系统触发，意味着非同一线程，需要用Dispatcher来操作UI&lt;/p&gt;
&lt;h2&gt;三、获取和控制系统媒体&lt;/h2&gt;
&lt;p&gt;好消息是，负责这部分的模块&lt;code&gt;GlobalSystemMediaTransportControlsSession&lt;/code&gt;公开可以任意使用，不受UWP平台限制。&lt;/p&gt;
&lt;h3&gt;1.获取媒体信息&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;var gsmtcsm = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync();//获取SMTC会话管理器
gsmtcsm.CurrentSessionChanged += xxx; //当前会话改变或退出时发生，微软对CurrentSession的解释是用户可能最希望控制的媒体会话，实测为Windows控制中心顶部显示的媒体，当有多个媒体时用户可以在此选择切换
...
var session = gsmtcsm.GetCurrentSession();
if(session == null)
    return; //当前没有注册的SMTC会话

//接下来操作session即可，下面仅提供参考信息

//媒体信息改变时发生，奇怪的是这些事件提供的参数e并没有任何信息
session.MediaPropertiesChanged += async (sender, e)=&amp;gt;{
    //触发事件时主动拉取信息
    var info = await _globalSMTCSession.TryGetMediaPropertiesAsync();
};
//播放状态改变时发生
session.PlaybackInfoChanged +=(sender,e)=&amp;gt;{
    var status = globalSMTCSession.GetPlaybackInfo().PlaybackStatus;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.控制媒体播放&lt;/h3&gt;
&lt;p&gt;直接调用即可&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;await session.TryPauseAsync();
await session.TryPlayAsync();
await session.TrySkipPreviousAsync();
await session.TrySkipNextAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;四、一些奇怪的地方&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;无法显示媒体来源，并且不会清空上一个来源的信息
可能是因为没有提供合法的UWP句柄，Windows虽然能确定是哪个exe调用的SMTC，但是拒绝直接显示exe的信息。逻辑上来说这个来源信息会被空覆盖掉，但是并没有。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;信息更新不一致和延时
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;系统显示的会话以及提供&lt;code&gt;GlobalSMTCSessionMng.&lt;/code&gt;获取的信息有时会不一致，二者都有可能和应用真实在播放的不一致，后者获取的封面图有时也会不一致。此外，&lt;code&gt;MusicProperty&lt;/code&gt;的更新有时并不会实时反馈到&lt;code&gt;GlobalSMTCSession&lt;/code&gt;的&lt;code&gt;Changed&lt;/code&gt;事件，我测试的时候当系统内存爆满(98% 我开了一堆浏览器标签页和4个vs)的时候，更新丢失的概率在70%左右，而资源充足时可以做到几乎即时更新。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;暂未实现点击跳转到App
正统UWP App的SMTC会话是可以点击跳转到App播放界面的，但是我并没有找到相关的事件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;奇怪的MediaId
Windows系统似乎通过这个来区分不同的媒体来源（明明可以获得调用者- -），神奇的是如果你为两个应用设置了同样的MediaId，那么只有两个都关闭时，SMTC会话才会释放。此外通过&lt;code&gt;GlobalSMTCSession.SourceAppUserModelId&lt;/code&gt;并不能获得你设置的MediaId，而是调用者的文件名&quot;xxx.exe&quot;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;五、使用我封装的库&lt;/h2&gt;
&lt;p&gt;Demo和库已经开源：&lt;br /&gt;
::github{repo=&quot;TwilightLemon/MediaTest&quot;}&lt;/p&gt;
&lt;p&gt;简单地将现有的解码器接入SMTC：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SMTCCreator? _smtcCreator = null;
...
 _smtcCreator ??= new SMTCCreator(&quot;MediaTest&quot;);
//修改播放状态
_smtcCreator.SetMediaStatus(SMTCMediaStatus.Playing);
//设置媒体信息
_smtcCreator.Info.SetAlbumTitle(&quot;AlbumTitle&quot;)
                    .SetArtist(&quot;Taylor Swift&quot;)
                    .SetTitle(&quot;Dancing With Our Hands Tied&quot;)
                    .SetThumbnail(&quot;https://y.qq.com/music/photo_new/T002R300x300M000003OK4yP2MBOip_1.jpg?max_age=2592000&quot;)
                    .Update();
//注册交互响应
_smtcCreator.PlayOrPause += _smtcCreator_PlayOrPause;
_smtcCreator.Previous += _smtcCreator_Previous;
_smtcCreator.Next += _smtcCreator_Next;//合适的时候调用释放资源_smtcCreator.Dispose();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;简单地控制系统媒体：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SMTCListener _smtcListener = null;
...
_smtcListener = await SMTCListener.CreateInstance();
_smtcListener.MediaPropertiesChanged += _smtcListener_MediaPropertiesChanged;
_smtcListener.PlaybackInfoChanged += _smtcListener_PlaybackInfoChanged;
_smtcListener.SessionExited += _smtcListener_SessionExited;
...
//媒体退出
 private void _smtcListener_SessionExited(object? sender, EventArgs e) { }

//播放状态改变
private void _smtcListener_PlaybackInfoChanged(object? sender, EventArgs e)
{
    Dispatcher.Invoke(() =&amp;gt;
    {
        var info = _smtcListener.GetPlaybackStatus();
        if (info == null) return;
        StatusTb.Text = info.ToString();
    });
}
//媒体信息改变
private void _smtcListener_MediaPropertiesChanged(object? sender, EventArgs e)
{
    Dispatcher.Invoke(async () =&amp;gt;
    {
        var info = await _smtcListener.GetMediaInfoAsync();
        if (info == null) return;
        TitleTb.Text = info.Title;
        ArtistTb.Text = info.Artist;
        AlbumTitleTb.Text = info.AlbumTitle;
        //获取封面图的方法
        if (info.Thumbnail != null)
        {
            var img = new BitmapImage();
            img.BeginInit();
            img.StreamSource = (await info.Thumbnail.OpenReadAsync()).AsStream();
            img.EndInit();
            ThumbnailImg.Source = img;
        }
    });
}
...
//控制播放
await _smtcListener.Previous();
await _smtcListener.Next();
await _smtcListener.Pause();
await _smtcListener.Play();
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;六、写在最后&lt;/h2&gt;
&lt;p&gt;参考资料：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/zh-cn/uwp/api/windows.media.systemmediatransportcontrols?view=winrt-26100&quot;&gt;SystemMediaTransportControls 类 (Windows.Media) - Windows UWP applications | Microsoft Learn&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/desktop-to-uwp-supported-api&quot;&gt;桌面应用中不支持 Windows 运行时 API - Windows 应用 |Microsoft学习 --- Windows Runtime APIs not supported in desktop apps - Windows apps | Microsoft Learn&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/uwp/api/windows.media.control.globalsystemmediatransportcontrolssessionmanager?view=winrt-26100&quot;&gt;GlobalSystemMediaTransportControlsSessionManager Class (Windows.Media.Control) - Windows UWP applications | Microsoft Learn&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>WPF 如何流畅地滚动ScrollViewer 简单实现下</title><link>https://blog.twlmgatito.com/posts/wpf-smooth-scrollviewer/</link><guid isPermaLink="true">https://blog.twlmgatito.com/posts/wpf-smooth-scrollviewer/</guid><description>WPF 流畅滚动ScrollViewer</description><pubDate>Sat, 13 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;看了看原生UWP的ScrollViewer，滑动很流畅(例如 开始菜单)，但是WPF自带的ScrollViewer滚动十分生硬..&lt;/p&gt;
&lt;p&gt;突发奇想，今天来实现一个流畅滚动的ScrollViewer.&lt;/p&gt;
&lt;h2&gt;一、目标&lt;/h2&gt;
&lt;p&gt;查阅网上的实现方法，要么直接重写控件，要么一堆Storyboard..很是无奈,还有些许bug.&lt;/p&gt;
&lt;p&gt;主要目标如下:&lt;/p&gt;
&lt;p&gt;1.能够流畅地滚动ScrollViewer   要求：有惯性的物理滚动 而不是 生硬的 内容滚动.&lt;/p&gt;
&lt;p&gt;2.在使用动画的过程中，避免多个begin导致卡顿&lt;/p&gt;
&lt;p&gt;3.好用(? )&lt;/p&gt;
&lt;h2&gt;二、准备工作&lt;/h2&gt;
&lt;p&gt;实现以上目标需要到的几件东西:&lt;/p&gt;
&lt;p&gt;1.对于Listbox之类的控件需要先将滚动方式变为 像素滚动  （若没有的话就可以不设置）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;ListBox
    VirtualizingPanel.ScrollUnit = &quot;Pixel&quot; 
    VirtualizingPanel.VirtualizationMode=&quot;Recycling&quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2.选一种动画使用的 缓动函数 这里推荐使用 CubicEase的EaseOut函数 (优点:接近于惯性滚动  对于CPU比较友好)&lt;/p&gt;
&lt;p&gt;3.要对ScrollViewer启用滚动动画 需要自己写附加属性:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static class ScrollViewerBehavior
{
      public static readonly DependencyProperty VerticalOffsetProperty = DependencyProperty.RegisterAttached(&quot;VerticalOffset&quot;, typeof(double), typeof(ScrollViewerBehavior), new UIPropertyMetadata(0.0, OnVerticalOffsetChanged));
      public static void SetVerticalOffset(FrameworkElement target, double value) =&amp;gt; target.SetValue(VerticalOffsetProperty, value);
      public static double GetVerticalOffset(FrameworkElement target) =&amp;gt; (double)target.GetValue(VerticalOffsetProperty);
      private static void OnVerticalOffsetChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) =&amp;gt; (target as ScrollViewer)?.ScrollToVerticalOffset((double)e.NewValue);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、编码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;//继承ScrollViewer 通过截获MouseWheel事件控制滚动
    public class MyScrollViewer : ScrollViewer
    {
        //记录上一次的滚动位置
        private double LastLocation = 0;
        //重写鼠标滚动事件
        protected override void OnMouseWheel(MouseWheelEventArgs e)
        {
            double WheelChange = e.Delta;
            //可以更改一次滚动的距离倍数 (WheelChange可能为正负数!)
            double newOffset = LastLocation - (WheelChange * 2);
            //Animation并不会改变真正的VerticalOffset(只是它的依赖属性) 所以将VOffset设置到上一次的滚动位置 (相当于衔接上一个动画)
            ScrollToVerticalOffset(LastLocation);
            //碰到底部和顶部时的处理
            if (newOffset &amp;lt; 0)
                newOffset = 0;
            if (newOffset &amp;gt; ScrollableHeight)
                newOffset = ScrollableHeight;

            AnimateScroll(newOffset);
            LastLocation = newOffset;
            //告诉ScrollViewer我们已经完成了滚动
            e.Handled = true;
        }
        private void AnimateScroll(double ToValue)
        {
            //为了避免重复，先结束掉上一个动画
            BeginAnimation(ScrollViewerBehavior.VerticalOffsetProperty, null);
            DoubleAnimation Animation = new DoubleAnimation();
            Animation.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut };
            Animation.From = VerticalOffset;
            Animation.To = ToValue;
            //动画速度
            Animation.Duration = TimeSpan.FromMilliseconds(800);
            //考虑到性能，可以降低动画帧数
            //Timeline.SetDesiredFrameRate(Animation, 40);
            BeginAnimation(ScrollViewerBehavior.VerticalOffsetProperty, Animation);
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用方法:直接创建 &lt;code&gt;&amp;lt;MyScrollViewer/&amp;gt;&lt;/code&gt;
如果是在ListBox中，可以通过修改模板的方式，把&lt;code&gt;&amp;lt;ScrollViewer/&amp;gt;&lt;/code&gt;换成&lt;code&gt;MyScrollViewer&lt;/code&gt;即可.&lt;/p&gt;
&lt;h2&gt;四、关于性能&lt;/h2&gt;
&lt;p&gt;按照上述方法实现的功能，CPU占用率基本稳定在10% ，但是要疯狂地上下滑动滚轮的话，25%+   但是动画还是很流畅的&lt;/p&gt;
&lt;p&gt;以下有几点可以改进的措施:&lt;/p&gt;
&lt;p&gt;滚动的CPU占用不只是对偏移量的一个计算，与你滚动的UI画面内容具有很大的关系&lt;/p&gt;
&lt;p&gt;例如大量的图片Image   3D对象    复杂的自定义控件 等等 都需要在滚动时计算并绘制。&lt;/p&gt;
&lt;p&gt;你可以尝试以下方法:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;启用ListBox的虚拟化技术&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于图像的绘制，可以通过降低其渲染度(可能会损坏图片质量 特别是低清小图 ),对你的图片对象使用:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RenderOptions.SetBitmapScalingMode(控件对象,BitmapScalingMode.LowQuality);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;就像上文中所说，可以通过降低渲染帧数来控制CPU占用率（可能会有失流畅度）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果能找到更好的缓动函数&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;参阅: &lt;a href=&quot;https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/advanced/optimizing-performance-controls?view=netframeworkdesktop-4.8&quot;&gt;优化控件性能 -WPF |Microsoft Docs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>C# .Net Core 3.1 中关于Process.Start 启动Url链接的问题</title><link>https://blog.twlmgatito.com/posts/dotnetcore-process-start-url/</link><guid isPermaLink="true">https://blog.twlmgatito.com/posts/dotnetcore-process-start-url/</guid><pubDate>Sun, 15 Mar 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;WPF 项目迁移到.Net Core中时居然出了一堆问题...(很无语)&lt;/p&gt;
&lt;p&gt;今天在使用的时候居然发现&lt;code&gt;Process.Start&lt;/code&gt;居然打不开Url链接了？ 报 找不到指定文件 的异常？！&lt;/p&gt;
&lt;h2&gt;一、bug重现&lt;/h2&gt;
&lt;p&gt;首先以.Net Core 3.1框架 中一个Console项目  打开百度为例:&lt;br /&gt;

运行然后你就会得到:&lt;br /&gt;
&lt;/p&gt;
&lt;p&gt;纳闷的是，这种方法打开Url在.Net Framework中是没问题的..&lt;/p&gt;
&lt;h2&gt;二、解决方法&lt;/h2&gt;
&lt;h4&gt;方案一：使用windows系统自带的资源管理器来打开Url&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;Process.Start(&quot;explorer&quot;, &quot;https://www.baidu.com&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你就可以得到一个浏览器窗口，打开着百度网页..&lt;br /&gt;
但是如果你的Url复杂一点的话，例如打开百度搜索what&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Process.Start(&quot;explorer&quot;, &quot;https://www.baidu.com/s?wd=what&quot;);
explorer表示不干，并直接给你抛了个文件管理器窗口
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以这个方法只可以用于打开简单的URL...&lt;/p&gt;
&lt;h4&gt;方案二：使用cmd中的start命令 可以打开任意形式的URL&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;string url = &quot;https://www.baidu.com/s?wd=what&quot;;
Process p = new Process();
p.StartInfo.FileName = &quot;cmd.exe&quot;;
p.StartInfo.UseShellExecute = false;    //不使用shell启动
p.StartInfo.RedirectStandardInput = true;//喊cmd接受标准输入
p.StartInfo.RedirectStandardOutput = false;//不想听cmd讲话所以不要他输出
p.StartInfo.RedirectStandardError = true;//重定向标准错误输出
p.StartInfo.CreateNoWindow = true;//不显示窗口
p.Start();

//向cmd窗口发送输入信息 后面的&amp;amp;exit告诉cmd运行好之后就退出
p.StandardInput.WriteLine(&quot;start &quot;+url + &quot;&amp;amp;exit&quot;);
p.StandardInput.AutoFlush = true;
p.WaitForExit();//等待程序执行完退出进程
p.Close();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OK&lt;/p&gt;
&lt;p&gt;以上的问题我只在Windows平台上找到过，不知Linux和OSX有没有...&lt;/p&gt;
&lt;p&gt;Thanks&lt;/p&gt;
</content:encoded></item><item><title>C# 简单地使用下 音频解码器Bass.Net</title><link>https://blog.twlmgatito.com/posts/csharp-use-audio-decoder-bassnet/</link><guid isPermaLink="true">https://blog.twlmgatito.com/posts/csharp-use-audio-decoder-bassnet/</guid><description>全平台、高效、小巧的音频解码器--Bass (非商用免费) 本文教程包括音频播放 缓存 使用Fx效果器等</description><pubDate>Wed, 11 Mar 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;在C#中有许多音频播放的方案，例如WinForm里调用系统自带MediaPlayer的COM组件和WPF的MediaPlayer(实质上还是WindowsMediaPlayer)&lt;br /&gt;
以及一堆API播放和DirectX （SDK一大堆）&lt;br /&gt;
于是我找到了适用于全平台、高效、小巧的音频解码器--Bass (主程序基于C++ C#可通过官方库Bass.Net调用)&lt;/p&gt;
&lt;h2&gt;一、开始&lt;/h2&gt;
&lt;p&gt;首先需要到官网下载&lt;code&gt;bass.dll&lt;/code&gt; 主程序文件(大约 257kb): http://www.un4seen.com/&lt;/p&gt;
&lt;p&gt;以及类库(.Net平台调用) : 你可以在 http://www.bass.radio42.com/bass_register.html  中使用你的邮箱即可注册到一个&lt;code&gt;key&lt;/code&gt;和下载&lt;code&gt;Bass.Net.dll&lt;/code&gt;(大约520kb)&lt;/p&gt;
&lt;p&gt;官方文档: http://www.bass.radio42.com/help/&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;P.S: &lt;code&gt;bass.dll&lt;/code&gt;需要放在程序主目录下 &lt;code&gt;Bass.Net.dll&lt;/code&gt;随意(添加到程序集引用)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;二、编码&lt;/h2&gt;
&lt;p&gt;在一切开始之前，你需要先注册程序和初始化Bass解码器:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using Un4seen.Bass;//添加引用
...
BassNet.Registration(&quot;你的邮箱&quot;, &quot;你注册到的Key&quot;);
Bass.BASS_Init(-1, 44100, BASSInit.BASS_DEVICE_CPSPEAKERS, /*窗口句柄 没有的话就IntPtr.Zero*/);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1. 实现简单的mp3播放&lt;/h3&gt;
&lt;p&gt;播放mp3有2种方式 :从&lt;code&gt;文件&lt;/code&gt;中加载、从&lt;code&gt;URL&lt;/code&gt;中加载&lt;/p&gt;
&lt;h4&gt;例1: 从文件中加载:&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;//---------调用到的方法------------
public static int BASS_StreamCreateFile(
    string file,//文件路径
    long offset,//偏移量，一般不怎么使用
    long length,//如果你使用了偏移量，定义一个偏移量之后的读取长度
    BASSFlag flags//以什么方式创建流
)
//----------------------------------
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;帮助链接和其他信息: http://www.bass.radio42.com/help/html/e49e005c-52c0-fc33-e5f9-f27f2e0b1c1f.htm&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//创建流的id，建议作为全局变量加入(如果是播放单文件)
private int stream =  -1024;//可以自己定义一个初始值
...
//从文件中创建一个简单的FLOAT音频流,返回流的id 便于控制和查询
stream = Bass.BASS_StreamCreateFile(你的文件完整路径, 0L, 0L, BASSFlag.BASS_SAMPLE_FLOAT);
... 开始播放
//参数:int 要播放的流的id,bool 是否在播放完成之后再重新播放
Bass.BASS_ChannelPlay(stream, false);
... 暂停播放
Bass.BASS_ChannelPause(stream);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;例2:从URL中加载:&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;stream = Bass.BASS_StreamCreateURL(url, 0, BASSFlag.BASS_SAMPLE_FLOAT, null, IntPrt.Zero);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只是加载音频流的方式改变了，其他的一致&lt;br /&gt;
如果你需要一些其他的功能(请求Url时的标头和下载回调)，请参阅以下:&lt;/p&gt;
&lt;h5&gt;1.添加URL请求标头:&lt;/h5&gt;
&lt;p&gt;很简单: 在url参数里添加即可，url与每一条标头之间用&quot;\r\n&quot;换行，例如:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;stream = Bass.BASS_StreamCreateURL(url+&quot;\r\n&quot;+&quot;一条标头，Header:Content&quot;+&quot;\r\n&quot;+&quot;再一条标头&quot;,...);
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.下载回调: 多用于缓存&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;//必须是全局变量，否则会被GC回收！
private DOWNLOADPROC _myDownloadProc;
private FileStream _fs = null;//写入文件的流
private byte[] _data; // 本地缓存

...
//添加调用
 _myDownloadProc = new DOWNLOADPROC(DownloadCallBack);
...

//下载回调，由Bass调用
private void DownloadCallBack(IntPtr buffer, int length, IntPtr user)
        {
            // file length
            long len = Bass.BASS_StreamGetFilePosition(stream, BASSStreamFilePosition.BASS_FILEPOS_END);
            // download progress
            long down = Bass.BASS_StreamGetFilePosition(stream, BASSStreamFilePosition.BASS_FILEPOS_DOWNLOAD);
            //可在此处添加下载进度的Callback

            if (_fs == null)
            {
                // 开始下载，打开文件流
               //坑:当你切歌的时候，Bass并不会继续下载而且会向你发送已经下载完成的标识，此时你得到的文件是不完整的!所以此处先作为.cache下载
                _fs = File.OpenWrite(保存的路径 + &quot;.cache&quot;);
            }
            if (buffer == IntPtr.Zero)
            {
                // 下载完成
                _fs.Flush();
                _fs.Close();
                _fs = null;
                FileInfo fi = new FileInfo(DLPath + &quot;.cache&quot;);
               //如果下载不完整的话就删除.cache
                if (fi.Length != len)
                {
                    fi.Delete();
                }
                else
                {
                    //如果下载完整的话就移动到缓存(下载)目录
                    fi.MoveTo(你的路径, true);
                    //这里可以做下载完成的回调
                }
            }
            else
            {
                //接受到下载数据，实质上是Bass传来数据的指针，C#根据指针从内存中复制数据
                // increase the data buffer as needed
                if (_data == null || _data.Length &amp;lt; length)
                    _data = new byte[length];
                // copy from managed to unmanaged memory
                Marshal.Copy(buffer, _data, 0, length);
                // write to file
                _fs.Write(_data, 0, length);
            }
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 获得和设置一些常规数据(播放进度、声音):&lt;/h3&gt;
&lt;h4&gt;1. 设置、获取音量:&lt;/h4&gt;
&lt;p&gt;坑：音量的设置是暂时性的，仅对于你输入的&lt;code&gt;stream&lt;/code&gt;,当你再次新建音频流时(例如切歌),音量会恢复默认!你可能需要记录下设置的音量并在下一次加载流时将音量设置Set到&lt;code&gt;stream&lt;/code&gt;中!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//设置音量 值在0~1之间 默认值为1
Bass.BASS_ChannelSetAttribute(stream, BASSAttribute.BASS_ATTRIB_VOL, value);

...

//获取当前音量 ref value
float value=0;
Bass.BASS_ChannelGetAttribute(stream, BASSAttribute.BASS_ATTRIB_VOL,ref value);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.播放进度和总长(时间):&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt; //长度
public TimeSpan GetLength
        {
            get
            {
                double seconds = Bass.BASS_ChannelBytes2Seconds(stream, Bass.BASS_ChannelGetLength(stream));
                return TimeSpan.FromSeconds(seconds);
            }
        }
//当前播放位置
public TimeSpan Position
        {
            get
            {
                double seconds = Bass.BASS_ChannelBytes2Seconds(stream, Bass.BASS_ChannelGetPosition(stream));
                return TimeSpan.FromSeconds(seconds);
            }
            set =&amp;gt; Bass.BASS_ChannelSetPosition(stream, value.TotalSeconds);
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3.获取FFT数据，你可以用这个数据来做频谱:&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;//获取256位的FFT数据，当然你可以选择更大的，但是也足够了
//坑:暂停播放时获得的FFT数据仍是没有暂停前的数据(停留在此位置的FFT)如果你需要做频谱图，在暂停时应该手动设置为0，因为Bass并不会在暂停时返回0
float[] fft = new float[256];
Bass.BASS_ChannelGetData(stream, fft, (int)BASSData.BASS_DATA_FFT256);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Bass&lt;/code&gt;并没有提供播放完成的回调，你需要设置一个&lt;code&gt;Timer&lt;/code&gt;来判断是否播放完成，理论上当当前位置=播放总长时算播放完成...&lt;/p&gt;
&lt;h3&gt;3. 更新设备和结束时&lt;/h3&gt;
&lt;p&gt;系统会将当前的默认设备放在集合的&lt;code&gt;[0]&lt;/code&gt;位，但是&lt;code&gt;Bass&lt;/code&gt;并不会自动更新设备也没有更新设备的事件回调（或是我没有找到?），所以你需要自己检查下有没有新的设备插入，你需要手动更新设备(如果需要):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public void UpdataDevice()
        {
            var data = Bass.BASS_GetDeviceInfos();
            int index = -1;
            for (int i = 0; i &amp;lt; data.Length; i++)
                if (data[i].IsDefault)
                {
                    index = i;
                    break;
                }
            if (!data[index].IsInitialized)
                Bass.BASS_Init(index, 44100, BASSInit.BASS_DEVICE_CPSPEAKERS, wind);
            var a = Bass.BASS_ChannelGetDevice(stream);
            if (a != index)
            {
                Bass.BASS_ChannelSetDevice(stream, index);
                Bass.BASS_SetDevice(index);
            }
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结束时，需要释放流和Bass解码器:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Bass.BASS_ChannelStop(stream);
Bass.BASS_StreamFree(stream);
Bass.BASS_Stop();
Bass.BASS_Free();
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.倍速播放和其他FX效果&lt;/h3&gt;
&lt;p&gt;为什么这里需要重新起个标题呢？&lt;br /&gt;
这个方法推翻了之前用到的&lt;code&gt;stream&lt;/code&gt;创建形式，但是控制播放暂停等方法不变。&lt;br /&gt;
首先你需要引入几个Bass的概念:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;音频流:&lt;code&gt;stream&lt;/code&gt;,用于播放音频，就是你控制播放暂停时传入的&lt;code&gt;stream&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;解码流 &lt;code&gt;decode&lt;/code&gt;,用于解码音频，然后传给&lt;code&gt;Fx效果器&lt;/code&gt;,&lt;code&gt;Fx效果器&lt;/code&gt;会返回给你一个&lt;code&gt;音频流&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;实现此方法，你需要下载FX扩展(就在bass.dll的地方下载 选择add-on 即可),大约85kb&lt;br /&gt;
添加using引用:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using Un4seen.Bass.AddOn.Fx;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建流:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//全局变量，解码流
private int decode;

decode = Bass.BASS_StreamCreateFile(你的文件, 0L, 0L, BASSFlag.BASS_STREAM_DECODE);
//相对于前面的你只需要把标签换成BASS_STREAM_DECODE即可，CreateURL亦是如此

//将解码流传入Fx效果器中，你将得到一个音频流
stream = BassFx.BASS_FX_TempoCreate(decode, BASSFlag.BASS_FX_FREESOURCE );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;设置Fx效果:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;value&lt;/code&gt;值在-90~0~5000之间  以百分制计算     例如 加速/减速 播放10%那么value=10,不用倍速播放value=0 减速 value=-10&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Bass.BASS_ChannelSetAttribute(stream, BASSAttribute.BASS_ATTRIB_TEMPO, value);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;获取Fx效果的方法和音量一致，更换标签为BASSAttribute.BASS_ATTRIB_TEMPO即可.&lt;/p&gt;
&lt;p&gt;更多的FX效果可以参阅: http://www.bass.radio42.com/help/html/90d034c4-b426-7f7c-4f32-28210a5e6bfb.htm 原理一样 修改标签和value值即可&lt;/p&gt;
</content:encoded></item></channel></rss>