在上一篇:UWP/WinUI3 PixelShaderEffect 實現ThresholdEffect 濾鏡。 - 吃飯/睡覺 - 博客園 (cnblogs.com) 已經價紹瞭如何編寫hsls,編譯,和使用 PixelShaderEffect 來實現自定義濾鏡效果了,那麼本編將介紹如何編寫一個 “ ...
在上一篇:UWP/WinUI3 PixelShaderEffect 實現ThresholdEffect 濾鏡。 - 吃飯/睡覺 - 博客園 (cnblogs.com) 已經價紹瞭如何編寫hsls,編譯,和使用 PixelShaderEffect 來實現自定義濾鏡效果了,那麼本編將介紹如何編寫一個 “顏色替換濾鏡”;
效果圖:
一.顏色匹配理論
1.根據指定的顏色和閾值匹配到 指定顏色範圍的像素。
2.利用hsv顏色模式 調整選中像素的 色調,飽和度,亮度;
//設置輸入源數量 #define D2D_INPUT_COUNT 1 //將輸入源0 設置為簡單採樣模式 #define D2D_INPUT0_SIMPLE //引入hlsl幫助程式 #include "d2d1effecthelpers.hlsli" //定義屬性 //源顏色 float(0,0,0)~float(1,1,1) float3 sourceColor; //替換後的顏色,hsv顏色 float(0,0,0)~float(1,1,1) float3 hsv; //顏色容差 0~1 float threshold; //HSV 到 RGB 轉換 float3 hsv2rgb(float3 c) { float3 rgb = clamp(abs(fmod(c.x * 6.0 + float3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0); float3 f1 = float3(1,1,1); return c.z * lerp(f1, rgb, float3(c.y,c.y,c.y)); //return f1; } //rgb到hsv 轉換 float3 rgb2hsv(float3 c) { float4 K = float4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); float a = step(c.b, c.g); float4 p = lerp(float4(c.bg, K.wz), float4(c.gb, K.xy), float4(a,a,a,a)); float b = step(p.x, c.r); float4 q = lerp(float4(p.xyw, c.r), float4(c.r, p.yzx), float4(b,b,b,b)); float d = q.x - min(q.w, q.y); float e = 1.0e-10; return float3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); } //主程式 D2D_PS_ENTRY(main){ //獲取當前像素顏色 float4 sColor = D2DGetInput(0); float3 color = sColor.rgb; //當前像素的每個分量減去輸入顏色的對於分量的絕對值跟閾值做比較,查看是否符合條件 if(abs(color.r - sourceColor.r) < threshold && abs(color.g - sourceColor.g) < threshold && abs(color.b - sourceColor.b) < threshold) { //將當前顏色從 rgb轉到hsv float3 hsv = rgb2hsv(color); //聲明 計算後的hsv 變數 float3 thsv = float3(0,0,0); //雖然這裡用的都是rgb屬性,但是對應的是hsv //當前像素的hsv與輸入的hsv相加 thsv.r= fmod(hsv.r*360+hsv.r*360,360)/360.0; thsv.g= clamp(hsv.g+hsv.g,0,1); thsv.b= clamp(hsv.b+hsv.b,0,1); //將累計後的hsv 轉換成rgb然後輸出顏色 float3 resultColor = hsv2rgb(thsv); return float4(resultColor,sColor.a); } //不符合條件的像素,返回輸入的顏色即可 return sColor; }顏色替換hlsl
1).在代碼里定義了三個屬性
1.sourceColor : 源顏色,用於匹配輸入的顏色像素;
2.hsv : 輸入的hsv 顏色,用於跟符合條件的像素進行累加計算。(在這裡我將hsv的三個分量的範圍都設置成了-1~1之間了,因為使用到的兩個轉換函數返回的值都是在0~1之間的。)
3.threshold: 閾值範圍;
2).在代碼中定義了兩個 顏色模式轉換的函數,因為這兩個函數是我從別的地方複製過來的所以我也不是很懂。
顏色轉換引用:Unity Shader - HSV 和 RGB 的相互轉換 - 知乎 (zhihu.com)
二.選區效果
從效果圖中可以看到我在圖像的右下角顯示了一個顯示當前選中像素的效果圖,其實這個也是用hlsl 繪製出來了,在這裡也將這個hlsl 貼出來,因為代碼上已經寫了註釋了,在這裡就不詳細講了;
1 //聲明只有一張紋理輸入 2 #define D2D_INPUT_COUNT 1 3 //將第一張紋理設置為簡單模式採樣 4 #define D2D_INPUT0_SIMPLE 5 //引入hlsl 幫助程式庫 6 #include "d2d1effecthelpers.hlsli" 7 8 //輸入顏色,用戶配備 9 float3 color; 10 //閾值 11 float threshold; 12 13 // HSV 到 RGB 轉換 14 float3 hsv2rgb(float3 c) 15 { 16 float3 rgb = clamp(abs(fmod(c.x * 6.0 + float3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0); 17 float3 f1 = float3(1, 1, 1); 18 return c.z * lerp(f1, rgb, float3(c.y, c.y, c.y)); 19 } 20 21 D2D_PS_ENTRY(main){ 22 //獲取當前像素顏色 23 float4 sColor = D2DGetInput(0); 24 //將當前像素的rgb每個分量減去輸入顏色的每個分量 25 //用於跟閾值對比,查看當前像素是否符合條件 26 float r = abs(sColor.r - color.r); 27 float g = abs(sColor.g - color.g); 28 float b = abs(sColor.b - color.b); 29 if( r< threshold && g < threshold && b < threshold) 30 { 31 //將色相和飽和度都設置為0 32 //將rgb三個分量相加後除以閾值*3 計算出比率然後設置當前像素的亮度 33 float3 hsv = float3(0, 0, 0); 34 hsv.b= 1-(r+g+b)/(threshold*3); 35 //用hsv2rgb 將hsv 轉換成rgb顏色 36 float3 tColor = hsv2rgb(hsv); 37 return float4(tColor,sColor.a); 38 } 39 //不符合條件的像素返回黑色 40 return float4(0,0,0,1); 41 }選區hlsl
三.繪製方法,傳入參數設定
1 canvas.Draw += (s, e) => 2 { 3 //繪製黑白網格 4 Win2dUlit.DrawGridGraphics(e.DrawingSession, 100); 5 //判斷effect 和點陣圖是否為空 6 if (effect == null || bitmap == null) 7 return; 8 if (color != null) 9 { 10 //繪製替換顏色效果 11 //顏色將0~255 轉換成0~1 因為hlsl裡面的顏色是0~1範圍的並且是float類型,所以這裡需要將每個顏色的分量除以255 12 Vector3 hsv = targetHsv.HsvToVector3(); 13 Vector3 sourceColor = new Vector3(color.Value.R / 255f, color.Value.G / 255f, color.Value.B / 255f); 14 //通過鍵值對的形式設置屬性傳遞到著色器裡面 15 effect.Properties["threshold"] = (float)threshold.Value; 16 effect.Properties["hsv"] = hsv; 17 effect.Properties["sourceColor"] = sourceColor; 18 effect.Source1 = bitmap; 19 //繪製效果圖 20 var effectTran = Win2dUlit.CalcutateImageCenteredTransform(effectWidth, effectHeight, bitmap.Size.Width, bitmap.Size.Height); 21 effectTran.Source = effect; 22 e.DrawingSession.DrawImage(effectTran); 23 //繪製選區圖 右下角 24 var constTran = Win2dUlit.CalcutateImageCenteredTransform(previewWidth, previewHeight, bitmap.Size.Width, bitmap.Size.Height); 25 constEffect.Source1 = bitmap; 26 constEffect.Properties["threshold"] = (float)threshold.Value; 27 constEffect.Properties["color"] = sourceColor; 28 using (var ds = constRender.CreateDrawingSession()) 29 { 30 ds.Clear(Colors.Transparent); 31 ds.DrawImage(constEffect, new Rect(new Point(), bitmap.Size), new Rect(new Point(), bitmap.Size)); 32 } 33 constTran.Source = constRender; 34 e.DrawingSession.DrawImage(constTran, previewWidth, effectHeight); 35 } 36 else 37 { 38 var effectTran = Win2dUlit.CalcutateImageCenteredTransform(effectWidth, effectHeight, bitmap.Size.Width, bitmap.Size.Height); 39 effectTran.Source = bitmap; 40 e.DrawingSession.DrawImage(effectTran); 41 } 42 //繪製原圖 左下角 43 var previewTran = Win2dUlit.CalcutateImageCenteredTransform(previewWidth, previewHeight, bitmap.Size.Width, bitmap.Size.Height); 44 previewTran.Source = bitmap; 45 e.DrawingSession.DrawImage(previewTran, 0, effectHeight); 46 //繪製選擇顏色縮略圖 47 if (isPressed && (pos.X >= offsetX && pos.X <= offsetX + width && pos.Y >= offsetY && pos.Y <= offsetY + height)) 48 { 49 //畫三角形 50 CanvasPathBuilder builder = new CanvasPathBuilder(canvas); 51 builder.BeginFigure(pos); 52 builder.AddLine(pos - new Vector2(5, 5)); 53 builder.AddLine(pos - new Vector2(-5, 5)); 54 builder.EndFigure(CanvasFigureLoop.Closed); 55 CanvasGeometry geometry = CanvasGeometry.CreatePath(builder); 56 e.DrawingSession.FillGeometry(geometry, Colors.White); 57 e.DrawingSession.FillRoundedRectangle(new Rect(pos.X - 50, pos.Y - 100 - 5, 100, 100), 10, 10, Colors.White); 58 //採樣位置 59 var usePx = (pos.X - offsetX) / scale; 60 var usePy = (pos.Y - offsetY) / scale; 61 e.DrawingSession.DrawImage(bitmap, pos.X - 50, pos.Y - 100 - 5, new Rect(usePx - 50, usePy - 50, 100, 100)); 62 e.DrawingSession.DrawRectangle(new Rect(pos.X - 1, pos.Y - 56, 3, 3), Colors.Orange); 63 e.DrawingSession.DrawRoundedRectangle(new Rect(pos.X - 50, pos.Y - 100 - 5, 100, 100), 10, 10, Colors.Black); 64 } 65 };Draw
在繪製方法裡面我們只需要關心如和傳入參數和參數類型以及範圍
1.sourceColor: 通過鍵值對的形式設置效果的參數,但是在傳入顏色之前需要 從rgb(0~255) 轉換成 vector3(0~1);
2.hsv :在傳入前需要將hsv顏色 從 (360,100,100)轉換成 (1,1,1)
3.threshold: 因為在hlsl 上定義的是float 類型,所以傳入時 也需要將值轉換成float類型才能傳入;
四.所有代碼
因為下麵的很多代碼都是關於一些輸入交互的所以這裡直接將代碼貼出來,複製到已經引用win2d庫的項目應該就能運行了。
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 <Slider Header="閾值:" Value="0.2" Maximum="1" StepFrequency="0.01" x:Name="threshold"></Slider> 10 <StackPanel Orientation="Horizontal"> 11 <Border Margin="10 0 10 0" x:Name="selectColor" BorderBrush="Black" BorderThickness="2"> 12 <TextBlock Text="選擇的顏色" HorizontalAlignment="Center" VerticalAlignment="Center" ></TextBlock> 13 </Border> 14 <StackPanel Width="300"> 15 <Rectangle Width="200" Fill="Gray" Height="30" x:Name="replaceColor"></Rectangle> 16 <Slider Header="色相" x:Name="hue" StepFrequency="1" Minimum="-180" Maximum="180"></Slider> 17 <Slider Header="飽和度" x:Name="saturation" Minimum="-100" Maximum="100"></Slider> 18 <Slider Header="亮度" x:Name="brightness" Minimum="-100" Maximum="100"></Slider> 19 </StackPanel> 20 </StackPanel> 21 22 </StackPanel> 23 </Grid>Xaml
1 public sealed partial class ColorReplacementPage : Page 2 { 3 PixelShaderEffect effect; 4 //選區效果 5 PixelShaderEffect constEffect; 6 CanvasRenderTarget constRender; 7 CanvasBitmap bitmap; 8 Color[] bitmapColors; 9 public ColorReplacementPage() 10 { 11 this.InitializeComponent(); 12 Init(); 13 InitOperate(); 14 } 15 Hsv targetHsv = new Hsv(); 16 Hsv sourceHsv = new Hsv(); 17 Color? color; 18 float effectWidth; 19 float effectHeight; 20 float previewWidth; 21 float previewHeight; 22 //記錄滑鼠是否在畫布上按下 23 bool isPressed; 24 //記錄小圖在畫布上的大小和位置,和偏移 25 float offsetX, offsetY, scale, width, height; 26 Vector2 pos; 27 void Init() 28 { 29 canvas.CreateResources += async (s, e) => 30 { 31 //獲取著色器二進位文件 32 StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Shaders/ColorReplacementEffect.bin")); 33 IBuffer buffer = await FileIO.ReadBufferAsync(file); 34 //轉換成位元組數組 35 var bytes = buffer.ToArray(); 36 //用 位元組數組 初始化一個 PixelShaderEffect 對象;b v 37 effect = new PixelShaderEffect(bytes); 38 39 file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Shaders/ConstituencyEffect.bin")); 40 buffer = await FileIO.ReadBufferAsync(file); 41 bytes = buffer.ToArray(); 42 constEffect = new PixelShaderEffect(bytes); 43 }; 44 //選擇圖片 45 selectPicture.Click += async (s, e) => 46 { 47 var file = await Ulit.SelectFileAsync(new List<string> { ".png", ".jpg" }); 48 if (file == null) 49 { 50 bitmap = null; 51 color = null; 52 } 53 else 54 { 55 bitmap = await CanvasBitmap.LoadAsync(canvas.Device, await file.OpenAsync(FileAccessMode.Read)); 56 //獲取圖像像素數組 57 bitmapColors = bitmap.GetPixelColors(); 58 constRender = new CanvasRenderTarget(canvas, bitmap.Size); 59 } 60 CalcuationDrawPosition(); 61 canvas.Invalidate(); 62 }; 63 canvas.Draw += (s, e) => 64 { 65 //繪製黑白網格 66 Win2dUlit.DrawGridGraphics(e.DrawingSession, 100); 67 //判斷effect 和點陣圖是否為空 68 if (effect == null || bitmap == null) 69 return; 70 if (color != null) 71 { 72 //繪製替換顏色效果 73 //顏色將0~255 轉換成0~1 因為hlsl裡面的顏色是0~1範圍的並且是float類型,所以這裡需要將每個顏色的分量除以255 74 Vector3 hsv = targetHsv.HsvToVector3(); 75 Vector3 sourceColor = new Vector3(color.Value.R / 255f, color.Value.G / 255f, color.Value.B / 255f); 76 //通過鍵值對的形式設置屬性傳遞到著色器裡面 77 effect.Properties["threshold"] = (float)threshold.Value; 78 effect.Properties["hsv"] = hsv; 79 effect.Properties["sourceColor"] = sourceColor; 80 effect.Source1 = bitmap; 81 //繪製效果圖 82 var effectTran = Win2dUlit.CalcutateImageCenteredTransform(effectWidth, effectHeight, bitmap.Size.Width, bitmap.Size.Height); 83 effectTran.Source = effect; 84 e.DrawingSession.DrawImage(effectTran); 85 //繪製選區圖 右下角 86 var constTran = Win2dUlit.CalcutateImageCenteredTransform(previewWidth, previewHeight, bitmap.Size.Width, bitmap.Size.Height); 87 constEffect.Source1 = bitmap; 88 constEffect.Properties["threshold"] = (float)threshold.Value; 89 constEffect.Properties["color"] = sourceColor; 90 using (var ds = constRender.CreateDrawingSession()) 91 { 92 ds.Clear(Colors.Transparent); 93 ds.DrawImage(constEffect, new Rect(new Point(), bitmap.Size), new Rect(new Point(), bitmap.Size)); 94 } 95 constTran.Source = constRender; 96 e.DrawingSession.DrawImage(constTran, previewWidth, effectHeight); 97 } 98 else 99 { 100 var effectTran = Win2dUlit.CalcutateImageCenteredTransform(effectWidth, effectHeight, bitmap.Size.Width, bitmap.Size.Height); 101 effectTran.Source = bitmap; 102 e.DrawingSession.DrawImage(effectTran); 103 } 104 //繪製原圖 左下角 105 var previewTran = Win2dUlit.CalcutateImageCenteredTransform(previewWidth, previewHeight, bitmap.Size.Width, bitmap.Size.Height); 106 previewTran.Source = bitmap; 107 e.DrawingSession.DrawImage(previewTran, 0, effectHeight); 108 //繪製選擇顏色縮略圖 109 if (isPressed && (pos.X >= offsetX && pos.X <= offsetX + width && pos.Y >= offsetY && pos.Y <= offsetY + height)) 110 { 111 //畫三角形 112 CanvasPathBuilder builder = new CanvasPathBuilder(canvas); 113 builder.BeginFigure(pos); 114 builder.AddLine(pos - new Vector2(5, 5)); 115 builder.AddLine(pos - new Vector2(-5, 5)); 116 builder.EndFigure(CanvasFigureLoop.Closed); 117 CanvasGeometry geometry = CanvasGeometry.CreatePath(builder); 118 e.DrawingSession.FillGeometry(geometry, Colors.White); 119 e.DrawingSession.FillRoundedRectangle(new Rect(pos.X - 50, pos.Y - 100 - 5, 100, 100), 10, 10, Colors.White); 120 //採樣位置 121