UWP/WinUI3 Win2d PixelShaderEffect 实现GradientMappingEffect (渐变映射滤镜)。
在上一篇文章UWP/WinUI3 Win2d PixelShaderEffect 实现ColorPlacementEffect (颜色替换) 滤镜。 - 吃饭/睡觉 - 博客园 (cnblogs.com)中实现了 ”颜色替换滤镜“,那么本文将制作一个“渐变映射滤镜”。
效果图:
一.渐变映射流程
1.将像素转换成灰度(0~1);
2.将转灰后的像素值 跟渐变图进行查找颜色;
3.将查找后的颜色进行替换原有颜色
流程图:
1 //定义输入源个数为2 2 #define D2D_INPUT_COUNT 2 3 //将第一张输入源设置为简单采样模式 4 #define D2D_INPUT0_SIMPLE 5 //将第二张输入源设置为复制采样模式 6 #define D2D_INPUT1_COMPLEX 7 //引入hlsl帮助程序库 8 #include "d2d1effecthelpers.hlsli" 9 10 //定义一个将颜色转换成灰度的函数 11 float GetGray(float3 color) 12 { 13 return (color.r + color.g + color.b) / 3.0; 14 } 15 //程序入口 16 D2D_PS_ENTRY(main){ 17 //获取当前像素颜色 18 float4 color = D2DGetInput(0); 19 //转换成灰度 20 float gray = GetGray(color.rgb); 21 //对渐变图(第二张输入位图)进行 百分比位置采样; 22 float3 targetColor = D2DSampleInput(1,float2(gray,0.5)).rgb; 23 return float4(targetColor,color.a); 24 }
二.hlsl 解析:
1.在头部定义里,声明了改hlsl 需要两张位图输入源,并将第一张(源图)的采样模式设置为SIMPLE(简单),因为我们对第一张输入源只用到了 D2DGetInput(){获取当前像素的颜色}函数; 将第二章(渐变图)的采样模式设置为COMPLEX(复杂),因为需要用到 D2DSampleInput(n,uv){按位置百分比获取指定位置的像素}函数;
2.在主程序函数里面,首先获取“源图”当前位置的像素,然后进行转成灰度,再根据灰度调用 D2DSampleInput 函数获取“渐变图”指定位置的像素颜色,然后返回颜色;
三.编译
1.如果不清楚怎么编译 hlsl供PixelShaderEffect使用的可以看这边文章:UWP/WinUI3 Win2d PixelShaderEffect 实现ThresholdEffect 滤镜。 - 吃饭/睡觉 - 博客园 (cnblogs.com)
四.使用PixelShaderEffect 制作“渐变映射”效果:
1.声明变量
1 //渐变映射效果 2 PixelShaderEffect effect; 3 //源图 4 CanvasBitmap bitmap; 5 //渐变图 6 CanvasRenderTarget gradientMap; 7 //效果渲染图 8 CanvasRenderTarget render; 9 //渐变颜色列表 10 Color[] colorMap;
2.在画布创建资源事件,首先将编译好的hlsl二进制代码读入到内存并转换成字节数组用于初始化一个PixelShaderEffect 对象,并创建一个 宽高255*1 大小的幕后绘制画布用于绘制渐变图;
1 canvas.CreateResources += async (s, e) => 2 { 3 //获取着色器二进制文件 4 StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Shaders/GradientMappingEffect.bin")); 5 IBuffer buffer = await FileIO.ReadBufferAsync(file); 6 //转换成字节数组 7 var bytes = buffer.ToArray(); 8 //用 字节数组 初始化一个 PixelShaderEffect 对象; 9 effect = new PixelShaderEffect(bytes); 10 //初始化渐变位图 11 gradientMap = new CanvasRenderTarget(canvas, 255, 1, 96); 12 colorMap = CreateGradientColors(); 13 CreateGradientRender(colorMap); 14 };
3.在选中图片的事件上 初始化一个跟源图一样大小的 幕后绘制画布,用于存储绘制效果图像。这个幕后绘制画布是用于先将效果绘制到这个临时画布上,然后再将这个临时画布绘制到屏幕上。备注:这里应该会有读者会问,为什么不将 PixelShaderEffect 效果直接绘制到canvas 上呢,要做这多此一举的步骤呢?因为PixelShaderEffect 会将整个画布的每个像素都会调用 hlsl 里面的执行过程,会出现我们预料之外的效果。直接将 effect 绘制到canvas上如下图:
1 //选择图片 2 selectPicture.Click += async (s, e) => 3 { 4 var file = await Ulit.SelectFileAsync(new List<string> { ".png", ".jpg" }); 5 if (file == null) 6 { 7 render = null; 8 bitmap = null; 9 } 10 else 11 { 12 bitmap = await CanvasBitmap.LoadAsync(canvas.Device, await file.OpenAsync(FileAccessMode.Read)); 13 //创建一个与源图大小一样的幕后绘制画布;用于存放绘制效果; 14 render = new CanvasRenderTarget(canvas, bitmap.Size); 15 } 16 canvas.Invalidate(); 17 };
4.绘制图像到画布:在绘制图像我们只需要将 “源图”和“渐变图” 赋值到effect的Source1,Source2上,并且需要将 “渐变图”的 Source2Interpolation 的插值模式设置为 NearestNeighbor(邻近)值;
1 canvas.Draw += (s, e) => 2 { 3 //绘制黑白网格 4 Win2dUlit.DrawGridGraphics(e.DrawingSession, 100); 5 //判断effect 和位图是否为空 6 if (effect == null || bitmap == null) 7 return; 8 var element = (FrameworkElement)s; 9 float effectWidth = (float)element.ActualWidth; 10 float effectHeight = (float)element.ActualHeight * 0.6f; 11 float previewWidth = effectWidth; 12 float previewHeight = (float)element.ActualHeight * 0.3f; 13 //设置两张位图源 14 effect.Source1 = bitmap; 15 effect.Source2 = gradientMap; 16 //设置映射图采样为 邻近采样,(默认为线性采样,效果会表现出意料之外) 17 effect.Source2Interpolation = CanvasImageInterpolation.NearestNeighbor; 18 //将效果绘制到render上 19 using (var ds = render.CreateDrawingSession()) 20 { 21 ds.Clear(Colors.Transparent); 22 ds.DrawImage(effect); 23 } 24 ////绘制效果图到画布上 25 var effectTran = Win2dUlit.CalcutateImageCenteredTransform(effectWidth, effectHeight, bitmap.Size.Width, bitmap.Size.Height); 26 effectTran.Source = render; 27 //直接将效果绘制到画布上,错误效果 28 //var effectTran = Win2dUlit.CalcutateImageCenteredTransform(effectWidth, effectHeight, bitmap.Size.Width, bitmap.Size.Height); 29 //effectTran.Source = effect; 30 //e.DrawingSession.DrawImage(effectTran); 31 32 //绘制原图 33 var previewTran = Win2dUlit.CalcutateImageCenteredTransform(previewWidth, previewHeight, bitmap.Size.Width, bitmap.Size.Height); 34 previewTran.Source = bitmap; 35 e.DrawingSession.DrawImage(previewTran, 0, effectHeight); 36 //绘制渐变图 37 var gradientTran = Win2dUlit.CalcutateImageCenteredTransform(effectWidth, element.ActualHeight * 0.1, gradientMap.Size.Width, gradientMap.Size.Height); 38 gradientTran.Source = gradientMap; 39 var rect = new Rect(0, previewHeight + effectHeight, effectWidth, element.ActualHeight * 0.1); 40 e.DrawingSession.DrawImage(gradientMap, rect); 41 };
五.全部代码
1 <Grid> 2 <Grid.RowDefinitions> 3 <RowDefinition></RowDefinition> 4 <RowDefinition Height="auto"></RowDefinition> 5 </Grid.RowDefinitions> 6 <canvas:CanvasControl x:Name="canvas"></canvas:CanvasControl> 7 <StackPanel Grid.Row="1"> 8 <Button Content="选择图片" x:Name="selectPicture"></Button> 9 <Button Content="创建渐变" x:Name="createGradient"></Button> 10 </StackPanel> 11 </Grid>
1 public sealed partial class GradientMappingPage : Page 2 { 3 //渐变映射效果 4 PixelShaderEffect effect; 5 //源图 6 CanvasBitmap bitmap; 7 //渐变图 8 CanvasRenderTarget gradientMap; 9 //效果渲染图 10 CanvasRenderTarget render; 11 //渐变颜色列表 12 Color[] colorMap; 13 public GradientMappingPage() 14 { 15 this.InitializeComponent(); 16 Init(); 17 } 18 19 void Init() 20 { 21 canvas.CreateResources += async (s, e) => 22 { 23 //获取着色器二进制文件 24 StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Shaders/GradientMappingEffect.bin")); 25 IBuffer buffer = await FileIO.ReadBufferAsync(file); 26 //转换成字节数组 27 var bytes = buffer.ToArray(); 28 //用 字节数组 初始化一个 PixelShaderEffect 对象; 29 effect = new PixelShaderEffect(bytes); 30 //初始化渐变位图 31 gradientMap = new CanvasRenderTarget(canvas, 255, 1, 96); 32 colorMap = CreateGradientColors(); 33 CreateGradientRender(colorMap); 34 }; 35 36 //选择图片 37 selectPicture.Click += async (s, e) => 38 { 39 var file = await Ulit.SelectFileAsync(new List<string> { ".png", ".jpg" }); 40 if (file == null) 41 { 42 render = null; 43 bitmap = null; 44 } 45 else 46 { 47 bitmap = await CanvasBitmap.LoadAsync(canvas.Device, await file.OpenAsync(FileAccessMode.Read)); 48 //创建一个与源图大小一样的幕后绘制画布;用于存放绘制效果; 49 render = new CanvasRenderTarget(canvas, bitmap.Size); 50 } 51 canvas.Invalidate(); 52 }; 53 54 canvas.Draw += (s, e) => 55 { 56 //绘制黑白网格 57 Win2dUlit.DrawGridGraphics(e.DrawingSession, 100); 58 //判断effect 和位图是否为空 59 if (effect == null || bitmap == null) 60 return; 61 var element = (FrameworkElement)s; 62 float effectWidth = (float)element.ActualWidth; 63 float effectHeight = (float)element.ActualHeight * 0.6f; 64 float previewWidth = effectWidth; 65 float previewHeight = (float)element.ActualHeight * 0.3f; 66 //设置两张位图源 67 effect.Source1 = bitmap; 68 effect.Source2 = gradientMap; 69 //设置映射图采样为 邻近采样,(默认为线性采样,效果会表现出意料之外) 70 effect.Source2Interpolation = CanvasImageInterpolation.NearestNeighbor; 71 //将效果绘制到render上 72 using (var ds = render.CreateDrawingSession()) 73 { 74 ds.Clear(Colors.Transparent); 75 ds.DrawImage(effect); 76 } 77 ////绘制效果图到画布上 78 var effectTran = Win2dUlit.CalcutateImageCenteredTransform(effectWidth, effectHeight, bitmap.Size.Width, bitmap.Size.Height); 79 effectTran.Source = render; 80 //直接将效果绘制到画布上,错误效果 81 //var effectTran = Win2dUlit.CalcutateImageCenteredTransform(effectWidth, effectHeight, bitmap.Size.Width, bitmap.Size.Height); 82 //effectTran.Source = effect; 83 //e.DrawingSession.DrawImage(effectTran); 84 85 //绘制原图 86 var previewTran = Win2dUlit.CalcutateImageCenteredTransform(previewWidth, previewHeight, bitmap.Size.Width, bitmap.Size.Height); 87 previewTran.Source = bitmap; 88 e.DrawingSession.DrawImage(previewTran, 0, effectHeight); 89 //绘制渐变图 90 var gradientTran = Win2dUlit.CalcutateImageCenteredTransform(effectWidth, element.ActualHeight * 0.1, gradientMap.Size.Width, gradientMap.Size.Height); 91 gradientTran.Source = gradientMap; 92 var rect = new Rect(0, previewHeight + effectHeight, effectWidth, element.ActualHeight * 0.1); 93 e.DrawingSession.DrawImage(gradientMap, rect); 94 }; 95 96 createGradient.Click += (s, e) => 97 { 98 colorMap = CreateGradientColors(); 99 CreateGradientRender(colorMap); 100 canvas.Invalidate(); 101 }; 102 103 } 104 /// <summary> 105 /// 创建渐变颜色数组 106 /// </summary> 107 /// <returns></returns> 108 Color[] CreateGradientColors() 109 { 110 var count = Random.Shared.Next(3, 10); 111 var colors = new Color[count]; 112 for (int i = 0; i < count; i++) 113 { 114 var r = (byte)Random.Shared.Next(255); 115 var g = (byte)Random.Shared.Next(255); 116 var b = (byte)Random.Shared.Next(255); 117 colors[i] = Color.FromArgb(255, r, g, b); 118 } 119 return colors; 120 } 121 122 /// <summary> 123 /// 绘制渐变位图 124 /// </summary> 125 /// <param name="mapColors"></param> 126 void CreateGradientRender(Color[] mapColors) 127 { 128 using (var ds = gradientMap.CreateDrawingSession()) 129 { 130 var canvasGradienStop = new CanvasGradientStop[mapColors.Length]; 131 for (int i = 0; i < mapColors.Length; i++) 132 { 133 canvasGradienStop[i] = new CanvasGradientStop 134 { 135 Color = mapColors[i], 136 Position = (float)i / (float)(mapColors.Length - 1) 137 }; 138 } 139 CanvasLinearGradientBrush linearBrush = new CanvasLinearGradientBrush(gradientMap.Device, canvasGradienStop) 140 { 141 StartPoint = new Vector2(0, 0), 142 EndPoint = new Vector2(256, 0), 143 }; 144 ds.FillRectangle(0, 0, 256, 1, linearBrush); 145 } 146 } 147 }