草庐IT

UWP/WinUI3 Win2d PixelShaderEffect 实现ThresholdEffect 滤镜。

吃饭/睡觉 2023-03-28 原文

在上一遍文章中已经介绍了PixelShaderEffect 用hlsl(着色器) 可以实现各种自定义滤镜效果了,本文将用 "ThresholdEffect" 来讲解如何编写,编译hlsl,然后使用PixelShaderEffect制作自定义滤镜。

效果图:

 

 

一.hlsl帮助程序介绍

  在写hlsl 代码前需要简单介绍下 “hlsl帮助程序”.通过学习了 hlsl帮助程序 后我们不需要将hlsl的所有知识都掌握了就可以写一写简单的hlsl代码了。hlsl帮助程序分为两部分,宏定义和函数。

  1.宏定义

D2D_INPUT_COUNT N 纹理输入个数。必须定义
D2D_INPUTn_SIMPLE  指定第n个纹理的为简单采样,默认为此定义,可选的
D2D_INPUTn_COMPLEX   指定第n个纹理为复杂采样.可选的
D2D_REQUIRES_SCENE_POSITION 指示着色器函数调用使用场景位置的值的帮助方法(即使用D2DGetScenePosition函数,必须要有该定义)
D2D_CUSTOM_ENTRY   着色器程序入口
#include "d2d1effecthelpers.hlsli" 引入 hlsl帮助程序

  2.函数

D2DGetInput(n) 获取第n张纹理的当前位置的像素,返回float4,即是rgba颜色。
D2DSampleInput(n,float2(x,y)) (按百分比位置进行采样)获取第n张纹理指定位置(xy按照百分比0~1)的像素颜色,返回float4,即rgba颜色。(需要定义纹理为复杂采样才能使用该函数)
D2DSampleInputAtOffset(n,float2(ox,oy)) (按绝对位置进行偏移采样)从输入坐标偏移的偏移量对第n张纹理进行采样(需要定义纹理为复杂采样)。例子:比如需要获取当前像素的左边像素可以使用该函数 D2DSampleInputAtOffset(0,float(-1,0))来获取左边像素的颜色;
D2DSampleInputAtPosition(n,float2(x,y)) (按绝对位置进行采样) 例子:比如输入的纹理图像大小的宽和高都为100,现在需要获取该纹理位置 50,50位置的像素可以使用该函数D2DSampleInputAtPosition(0,float2(50,50));(需要定义纹理为复杂采样)
D2DGetInputCoordinate(n) 获取当前像素在屏幕上的坐标(相对位置0~1)(需要定义纹理为复杂采样)
D2DGetScenePosition()   获取当前像素在屏幕上的坐标(绝对位置)(需要定义 D2D_REQUIRES_SCENE_POSITION宏)
D2D_PS_ENTRY 函数

 一个宏,用于定义具有给定函数名称的像素着色器入口点。

  hlsl帮助程序文档:HLSL 帮助程序 - Win32 apps | Microsoft Docs

  hlsl文档:高级着色器语言 (HLSL) - Win32 apps | Microsoft Docs

二.编写hlsl代码

  在项目程序中右键-》添加新建项-》常规-》文本文件 并将文件名改成ThresholdEffect.hlsl

  在代码中用宏定义了 输入一张纹理,并且将纹理的采样模式定义为简单模式,通过#include 引入hlsl帮助程序。

然后在声明了 三个变量 hreshold,startColor,endColor;

  声明一个 getGray(in float3 color) 函数将颜色的各个分量相加再除以3 并且返回;

  最后定义了一个 D2D_PS_ENTRY 函数,该函数是着色器程序的入口函数,在函数里面调用了 D2DGetInput(0)函数获取第0张纹理的当前像素,然后通过调用getGray 函数将像素的颜色转换成灰度值后跟 threshold 比较再决定返回那个颜色。

注意:函数的声明需要写在调用前,否则编译的时候会找不到函数而报错;

//定义输入纹理数量为 1张
#define D2D_INPUT_COUNT 1
//定义第一张纹理的采样模式为简单模式
#define D2D_INPUT0_SIMPLE
//引入 hlsl帮助程序
#include "d2d1effecthelpers.hlsli"

//定义属性
//阈值
float threshold;
//默认第一个颜色为白色
float4 startColor = float4(1,1,1,1);
//默认第二个颜色为黑色
float4 endColor = float4(0, 0, 0, 1);
//将输入颜色转换成灰度 (r+g+b)/3
float getGray(in float3 color)
{
    float gray = (color.r + color.g + color.b) / 3;
    return gray;
}
//程序入口
D2D_PS_ENTRY(main){
    //获取当前位置的输入纹理像素颜色
float3 color = D2DGetInput(0).rgb;
float gray = getGray(color);
    if(gray<=threshold){
    return endColor;
    }
    return startColor;
}

三.编译hlsl

  现在我们来将上面写好的hlsl 编译成二进制文件。

  1.记事本方式打开我们报错好的 ThresholdEffect.hlsl 文件查看它的编码格式是否是 UTF-8 ,如果不是则需要将编码改成UTF-8后保存。如果编码不对的情况下会在编译时报错说”找不到任何代码“.

 

 

 

 

 

  2.在vs工具栏中找到 工具-》命令行-》开发者提示    然后将位置切换到存放 ThresholdEffect.hlsl 文件的目录下,

然后输入以下命令进行编译,如果编译成功就会在存放目录下看到会多了一个ThresholdEffect.bin的文件,这个文件就是将hlsl编译好的二进制文件了:

set INCLUDEPATH="%WindowsSdkDir%\Include\%WindowsSDKVersion%\um"
 fxc ThresholdEffect.hlsl /nologo /T lib_4_0_level_9_3_ps_only /D D2D_FUNCTION /D D2D_ENTRY=main /Fl ThresholdEffect.fxlib /I %INCLUDEPATH%
 fxc ThresholdEffect.hlsl /nologo /T ps_4_0_level_9_3 /D D2D_FULL_SHADER /D D2D_ENTRY=main /E main /setprivate ThresholdEffect.fxlib /Fo:ThresholdEffect.bin /I %INCLUDEPATH%
del ThresholdEffect.fxlib

  编译命令就不在这里展开讲了感兴趣的可以 看文档:像素阴影效果构造函数 (microsoft.github.io) 里面有介绍;

 

 

 

 

 

 编译成功

 

 四.导入编译好的bin文件,然后右键文件->属性,将复制到输出目录改为 始终复制;

 

 五.xaml界面

  在界面中放置了一个 CanvasControl 控件用于显示绘制内容,在下面添加选择图片按钮,更改阈值的slider,和两个颜色选择器;

<Grid>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition Height="auto"></RowDefinition>
        </Grid.RowDefinitions>
        <canvas:CanvasControl x:Name="canvas"></canvas:CanvasControl>
        <StackPanel Grid.Row="1">
            <Button Content="选择图片" x:Name="selectPicture"></Button>
            <Slider Header="阈值:" Value="0" Maximum="1" StepFrequency="0.01" x:Name="threshold"></Slider>
            <StackPanel Orientation="Horizontal">
                <Button Content="颜色一" >
                    <Button.Flyout>
                        <Flyout>
                            <ColorPicker Color="White" x:Name="cp1"></ColorPicker>
                        </Flyout>
                    </Button.Flyout>
                </Button>
                <Rectangle Width="50" Fill="{Binding ElementName=cp1,Path=Color,Mode=TwoWay,Converter={StaticResource colorToBrush}}"></Rectangle>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <Button Content="颜色二" >
                    <Button.Flyout>
                        <Flyout>
                            <ColorPicker Color="Black" x:Name="cp2"></ColorPicker>
                        </Flyout>
                    </Button.Flyout>
                </Button>
                <Rectangle  Width="50" Fill="{Binding ElementName=cp2,Path=Color,Mode=TwoWay,Converter={StaticResource colorToBrush}}"></Rectangle>
            </StackPanel>
        </StackPanel>
    </Grid>
xaml

六.后台代码

  1.在 canvas的CreateResource 事件中读取bin文件中的字节数组并创建一个 PixelShaderEffect 对象;

  2.添加选择图片事件用于选择一张输入的源图;

  3.绘制图像,effect 对象通过 Properties 键值对的形式对着色器里面的属性进行赋值。这里需要注意 着色器里面的颜色范围是 0~1 并且是float类型,所以需要将c#里面的颜色的每个分量都除以255,转换成Vector4结构;

  4.监听Slider 和颜色选择器 的更改并重新绘制显示图像;

public sealed partial class ThresholdEffectPage : Page
    {
        PixelShaderEffect effect;
        CanvasBitmap bitmap;
        public ThresholdEffectPage()
        {
            this.InitializeComponent();
            Init();
        }

        void Init()
        {
            canvas.CreateResources += async (s, e) =>
             {
                 //获取着色器二进制文件
                 StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Shaders/ThresholdEffect.bin"));
                 IBuffer buffer = await FileIO.ReadBufferAsync(file);
                 //转换成字节数组
                 var bytes = buffer.ToArray();
                 //用 字节数组 初始化一个 PixelShaderEffect 对象;
                 effect = new PixelShaderEffect(bytes);

             };

            //选择图片
            selectPicture.Click += async (s, e) =>
            {
                var file = await Ulit.SelectFileAsync(new List<string> { ".png", ".jpg" });
                if (file == null)
                    bitmap = null;
                else
                    bitmap = await CanvasBitmap.LoadAsync(canvas.Device, await file.OpenAsync(FileAccessMode.Read));
                canvas.Invalidate();
            };

            canvas.Draw += (s, e) =>
            {
                //绘制黑白网格
                Win2dUlit.DrawGridGraphics(e.DrawingSession, 100);
                //判断effect 和位图是否为空
                if (effect == null || bitmap == null)
                    return;
                var element = (FrameworkElement)s;
                float effectWidth = (float)element.ActualWidth;
                float effectHeight = (float)element.ActualHeight * 0.7f;
                float previewWidth = effectWidth;
                float previewHeight = (float)element.ActualHeight * 0.3f;
                //绘制原图
                var previewTran = Win2dUlit.CalcutateImageCenteredTransform(previewWidth, previewHeight, bitmap.Size.Width, bitmap.Size.Height);
                previewTran.Source = bitmap;
                e.DrawingSession.DrawImage(previewTran, 0, effectHeight);

                //颜色将0~255 转换成0~1 因为hlsl里面的颜色是0~1范围的并且是float类型,所以这里需要将每个颜色的分量除以255
                Vector4 startColor = new Vector4(cp1.Color.R / 255f, cp1.Color.G / 255f, cp1.Color.B / 255f, 1f);
                Vector4 endColor = new Vector4(cp2.Color.R / 255f, cp2.Color.G / 255f, cp2.Color.B / 255f, 1f);
                //通过键值对的形式设置属性传递到着色器里面
                effect.Properties["threshold"] = (float)threshold.Value;
                effect.Properties["startColor"] = startColor;
                effect.Properties["endColor"] = endColor;
                effect.Source1 = bitmap;

                //绘制效果图
                var effectTran = Win2dUlit.CalcutateImageCenteredTransform(effectWidth, effectHeight, bitmap.Size.Width, bitmap.Size.Height);
                effectTran.Source = effect;
                e.DrawingSession.DrawImage(effectTran);
            };

            threshold.ValueChanged += (s, e) => canvas.Invalidate();
            //这里遇到问题了 说找不到 WinRT.ObjectReferenceValue ABI.Windows.Foundation.TypedEventHandler`2.CreateMarshaler2(Windows.Foundation.TypedEventHandler`2<!0,!1>)'。”
            //cp1.ColorChanged += (s, e) =>
            //{
            //    //if (canvas.IsLoaded)
            //    //    canvas.Invalidate();
            //};
            //cp2.ColorChanged += (s, e) =>
            //{
            //    //if (canvas.IsLoaded)
            //    //    canvas.Invalidate();
            //};

        }
    }
ThresholdEffectPage

结语:

  现在已经将PixelShaderEffect 的整个使用过程都讲解完了。下一篇讲解 "ReplaceColorEffect";

PixelShaderEffectThresholdEffectspancolorstyle.NET技术

有关UWP/WinUI3 Win2d PixelShaderEffect 实现ThresholdEffect 滤镜。的更多相关文章

  1. u盘安装系统(win10为例) - 2

    下载微PE工具箱进入官网下载微PE工具箱-下载 安装好后,打开微PE工具箱客户端,选择安装PE到U盘 PE壁纸可选择自己喜欢的壁纸,勾选上包含DOS工具箱,个性化盘符图标 下载原版系统进入网站下载镜像NEXT,ITELLYOU如果没有账号,注册一下就好进入选择开始使用选择win10 这里我们选择消费者版,用迅雷把BT种子下载下来 下面的两个盘符,是PE工具箱安装进U盘后,分成的盘符,注意EFI的盘符,这里面不能删东西,也不能添东西,另一个盘符可以当做正常的U盘空间使用,我们现在需要把下载下来的景象文件复制到正常的U盘空间中去 这个时候我们的系统U盘就只做好了 安装系统我们将U盘插入电脑,开机,

  2. Win10 / 11新电脑最简单跳过联网激活和使用本地账户登录方法 - 2

    跳过联网激活:OOBE界面直接按Ctrl+Shift+F3进入审核模式。这样就可以直接进入系统进行一些硬件测试等,而不用联网激活导致新机无法退货。需要注意的是,在审核模式下进行的一些操作都会保留,并不会在退出后自动还原!安装的软件在正常开机进系统后还会看见!如果电脑确实没连互联网又不想强行跳过OOBE(网上很多教程会叫你直接结束OOBE进程,但这是不推荐的,因为一些厂商自带优化程序和系统初始化设置在后面都会应用,对于笔记本跳过的话你会发现驱动和内置应用都没有装上。其实这部分脚本就在系统盘的Recovery隐藏文件夹下),可以参考以下方式:https://www.landiannews.com/

  3. ruby - 安装gem : Couldn't reserve space for cygwin's heap, Win32错误487错误 - 2

    我正在尝试在我的机器上安装win32-apigem,但在构建native扩展时我遇到了一些问题:$geminstallwin32-api--no-ri--rdocTemporarilyenhancingPATHtoincludeDevKit...Buildingnativeextensions.Thiscouldtakeawhile...C:\Programs\dev_kit\bin\make.exe:***Couldn'treservespaceforcygwin'sheap,Win32error0ERROR:Errorinstallingwin32-api:ERROR:Failed

  4. Ruby 1.9 - 没有这样的文件可以加载 'win32/open3' - 2

    我在Windows上运行ruby​​1.9.2并试图移植在Ruby1.8中工作的代码。该代码使用以前运行良好的Open4.popen4。对于1.9.2,我做了以下事情:通过geminstallPOpen4安装了POpen4需要POpen4通过require'popen4'尝试像这样使用POpen4:Open4.popen4("cmd"){|io_in,io_out,io_er|...}当我这样做时,我得到了错误:nosuchfiletoload--win32/open3如果我尝试安装win32-open3(geminstallwin32-open3),我会收到错误消息:win32-op

  5. 使用Python Win32COM如何获取对图表数据表的引用? - 2

    使用PythonWin32COM如何获取对图表数据表的引用?我可以使用数据表创建图表(PowerPoint将其弹出在单独的窗口中),例如:importwin32comfromMSOimportconstantsasmsoconstApplication=win32com.client.Dispatch("PowerPoint.Application")Application.Visible=TruePresentation=Application.Presentations.Add()FirstSlide=Presentation.Slides.Add(1,12)...noproblemadd

  6. 如何使用蓝牙连接将字符从UWP应用程序传输到Android应用? - 2

    我正在为RaspberryPi开发其UWP应用程序的应用程序,因此我想从UWP应用程序发送和接收字符到Android应用程序。因此,请告诉我什么是UWP应用程序的代码。提前致谢:)看答案此站点向您展示如何发送和接收串行蓝牙数据。github上的来源

  7. win10系统下Edge浏览器搜索引擎[必应]和新建标签页被篡改百度的一种解决方式 - 2

    一、我的情况:win10系统下Edge浏览器搜索引擎[必应]和新建标签页被篡改百度的搜索引擎和百度的页面我的解决方案步骤如下:1.检查电脑管家的浏览器保护检查一下你的电脑是否后台开启了电脑管家;如果是,则检查一下是否开启了浏览器保护。由于我是联想的笔记本,自带联想管家,一直没有关闭过它,以我的电脑为例,进行关闭,如下图所示。其他的电脑管家软件,应该也有类似的功能,耐心找一下就能找到啦。2.在Edge浏览中进行相关设置先点击浏览器右上角的三个点,然后找到“设置”,然后进入设置界面。在当前页面手动搜索“搜索引擎”,然后选择需要的搜索引擎“必应”,然后点击管理搜索引擎。进入管理搜索引擎界面后,理论上

  8. 【修电脑】VMware 从GHO文件备份恢复Win10/Win7系统 - 2

    【修电脑】VMware从GHO文件备份恢复Win10/Win7系统注意参考硬盘知识一、硬盘接口的分类二、硬盘的分类按照硬盘材质分为两大类按照接口类型区分boot启动知识LegacyBIOS引导uefi引导启动流程查看系统的引导启动方式1.VMware新建win10x64系统2.制作老毛桃U盘winpe3.VMwarewin10从winpe老毛桃U盘启动4.GHO文件还原系统(失败,勿复现)5.GHO转vmdk6.成功实现经验总结注意本文仅供参考学习,任何因阅读者操作导致的数据损失和破坏,本文作者概不负责!参考GHOST文件如何导入虚拟机硬盘知识一、硬盘接口的分类硬盘接口通常分为五种类型:SAT

  9. win 10 强制禁用驱动程序签名 - 2

    文章目录方法1,通过组策略禁用(未验证)1、首先按下键盘“Win+R”打开运行。2、接着在其中输入“gpedit.msc”回车打开组策略。3、然后进入计算机配置下“用户配置”中的“管理模板”4、再打开“系统”下的“驱动程序安装”5、进入后,双击打开其中的“设备驱动程序的代码签名”6、最后勾选“已启用”并将选项改成“忽略”再确定保存即可禁用强制签名。方法2(临时禁用)1、打开并登录操作系统左下角。开始菜单上单击选择设置。2、在Windows设置页面选择更新和安全。3、在更新和安全页面选择左侧的恢复标签,在右侧选择立即重新启动。4、在新的启动页面选择疑难解答。5、在疑难解答页面选择高级选项。6、在

  10. javascript - 获取 OS Win 7 用户名 Javascript - 2

    是否可以在浏览器IE、Chrome、Firefox、Opera中使用Javascript获取Windows用户名和PCName? 最佳答案 没有。此类信息不会暴露给浏览器中的javascript引擎。 关于javascript-获取OSWin7用户名Javascript,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8802602/

随机推荐