WPF BitmapSource Alpha通道偏移

时间:2017-07-05 16:31:01

标签: c# wpf bitmap

我有一张BitmapSource图像,我希望有效地将Alpha通道偏移一个固定的数量,即Alpha=Floor(Alpha-x)。什么是最有效的方法。我尝试使用DrawingContext.PushOpacity()然后使用DrawImage(),但这会增加不透明度,我真的想要做一个附加偏移

更新

我正在尝试在显示屏上更改文字时实现效果。我们的想法是在设置新值时淡化前一个文本值。我有一个粗略的实现,但我想在这个实现上输入或者是否有更好的策略。效果应如何显示的示例(使用静止图像很难)enter image description here

代码

    public class TransitionalText : FrameworkElement
    {
        public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(TransitionalText), new FrameworkPropertyMetadata(String.Empty, FrameworkPropertyMetadataOptions.None, OnTextPropertyChanged));
        public static readonly DependencyProperty TransitionProperty = DependencyProperty.Register("Transition", typeof(Int16), typeof(TransitionalText), new UIPropertyMetadata((Int16)255,OnTransitionPropertyChanged));

        private RenderTargetBitmap textCache, renderBitmap;
        private byte[] transitionCache;

        private void DrawText()
        {
            int stride = renderBitmap.PixelWidth * ((renderBitmap.Format.BitsPerPixel + 7) >> 3);

            // Copy Current Render Scene To Cache
            renderBitmap.CopyPixels(transitionCache, stride, 0);

            // Clear Cached Text Bitmap
            textCache.Clear();

            // Render New Text Value
            DrawingVisual drawingVisual = new DrawingVisual();
            DrawingContext drawingContext = drawingVisual.RenderOpen();
            FormattedText formattedText = new FormattedText(Text, System.Globalization.CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface(new FontFamily("lucidia"), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal), 32, Brushes.Black);

            drawingContext.DrawText(formattedText, new Point(0, 0));
            drawingContext.Close();
            textCache.Render(drawingVisual);

            // Create Opacity Mask
            byte[] opacityMask = new byte[renderBitmap.PixelHeight * stride];

            // Remove Overlapping Pixels
            for (int i=3; i<opacityMask.Length; i+=4)
            {
                transitionCache[i] -= (transitionCache[i] > opacityMask[i]) ? opacityMask[i] : transitionCache[i];
            }

        }

        private void FadeTransition(int fadeValue)
        {
            for (int i = 3; i < transitionCache.Length; i += 4)
            {
                transitionCache[i] -= (byte)((transitionCache[i] > fadeValue) ? fadeValue : transitionCache[i]);
            }
        }

        private void Render()
        {
            renderBitmap.Clear();
            DrawingVisual drawingVisual = new DrawingVisual();
            DrawingContext drawingContext = drawingVisual.RenderOpen();

            // Draw Transition 
            drawingContext.DrawImage(BitmapSource.Create(renderBitmap.PixelWidth, renderBitmap.PixelHeight, renderBitmap.DpiX, renderBitmap.DpiY, renderBitmap.Format, renderBitmap.Palette, transitionCache, renderBitmap.PixelWidth * ((renderBitmap.Format.BitsPerPixel + 7) >> 3)), new Rect(RenderSize));
            drawingContext.DrawImage(textCache, new Rect(RenderSize));
            drawingContext.Close();

            renderBitmap.Render(drawingVisual);

        }


        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);


            renderBitmap = new RenderTargetBitmap((int)RenderSize.Width, (int)RenderSize.Height, 96, 96, PixelFormats.Pbgra32);
            textCache = (RenderTargetBitmap)renderBitmap.Clone();

            int stride = renderBitmap.PixelWidth * ((renderBitmap.Format.BitsPerPixel + 7) >> 3);
        transitionCache = new byte[renderBitmap.PixelHeight * stride];

            DrawText();
            Render();

            drawingContext.DrawImage(renderBitmap, new Rect(RenderSize));

        }

        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((TransitionalText)d).OnTextPropertyChanged((string)e.NewValue);
        }
        private void OnTextPropertyChanged(string newText)
        {
            if (renderBitmap!=null)
            {
                DrawText();
                Int16Animation intAnimation = new Int16Animation(255, 0, TimeSpan.FromMilliseconds(150));
                BeginAnimation(TransitionProperty, intAnimation);
            }

        }

        private static void OnTransitionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((TransitionalText)d).OnTransitionPropertyChanged((Int16)e.OldValue, (Int16)e.NewValue);
        }

        private void OnTransitionPropertyChanged(int oldValue, int newValue)
        {
            if (newValue < oldValue)
            {
                FadeTransition(oldValue - newValue);
                Render();
            }
        }
    }

我知道我可以通过参数化字体等做得更好但我想知道我不是先浪费时间。只要变化的变化率不是太快(在10ms看起来不太好,但是那个测试仅仅是DispatcherTimer,那么这种方法很有效,所以我认为渲染与执行时间相冲突计时器事件)

如果有更好的方法来实现像CustomShader等

这样的结果,我会感兴趣

由于

1 个答案:

答案 0 :(得分:2)

您打算如何使用生成的像素数据非常重要。

自定义效果

如果您只需要为渲染目的而偏移alpha(听起来就像这样),您可以创建自定义着色器效果来完成此操作,而不必处理修改像素数据。如果值随时间变化,并且您希望避免频繁重写位图,则此方法特别有用。

如果您从未在WPF中创建自定义着色器效果,则可以阅读有关该主题的一些文章。它主要涉及编写和编译HLSL像素着色器,包括编译的着色器作为应用程序资源,创建扩展ShaderEffect的C#类,并将效果连接到着色器。

这是一个快速未经测试的模拟器,理论上具有您正在寻找的效果。

<强>着色

sampler2D inputSampler : register(S0);
float alpha : register(C0);

float4 main(float2 uv : TEXCOORD) : COLOR
{
    float4 color = tex2D(inputSampler, uv);
    return float4(color.rgb, clamp(color.a + alpha, 0, 1));
}

<强>效果

class AddAlphaEffect : ShaderEffect
{
    // The Input property is special and will automatically receive the content of the element being rendered
    public Brush Input
    {
        get { return (Brush)GetValue(InputProperty); }
        set { SetValue(InputProperty, value); }
    }
    public static readonly DependencyProperty InputProperty = RegisterPixelShaderSamplerProperty("Input", typeof(AddAlphaEffect), 0);

    public float Alpha
    {
        get { return (Point)GetValue(CenterProperty); }
        set { SetValue(CenterProperty, value); }
    }
    public static readonly DependencyProperty AlphaProperty = DependencyProperty.Register("Alpha", typeof(float), typeof(AddAlphaEffect),
        new PropertyMetadata(0.0f, PixelShaderConstantCallback(0)));

    public AddAlphaEffect()
    {
        // Reference the compiled shader here, which should be included as a resource in your applciation.
        // ResourceHelper is my own utility that formats URIs for me. The returned URI
        // string will be something like /AssemblyName;component/Effects/AddAlpha.ps
        PixelShader = new PixelShader() { UriSource = ResourceHelper.GetResourceUri("Effects/AddAlpha.ps", relative: true)};

        UpdateShaderValue(AlphaProperty);
    }
}

<强>用法

<Image
    Source="some image">
    <Image.Effect>
        <effects:AddAlphaEffect
            Alpha="0.2" />
    </Image.Effect>
</Image>

可写位图

如果您想要修改数据并保持一段时间,因为它没有经常更改(或者您想将其保存),那么您可能希望使用WritableBitmap并更新像素数据直接。

来自MSDN documentation

  

调用其中一个WritePixels重载以自动更新和显示后台缓冲区中的内容。

     

为了更好地控制更新,以及对后台缓冲区的多线程访问,请使用以下工作流程。

     
      
  1. 调用Lock方法以保留后台缓冲区以进行更新。
  2.   
  3. 通过访问BackBuffer属性获取指向后台缓冲区的指针。
  4.   
  5. 将更改写入后台缓冲区。其他线程可能会将更改写入   WriteableBitmap被锁定时的后台缓冲区。
  6.   
  7. 调用AddDirtyRect方法以指示已更改的区域。
  8.   
  9. 调用Unlock方法释放后台缓冲区并允许演示文稿   屏幕。
  10.   

请注意,由于WPF中自定义着色器效果的性能较差,WritableBitmap方法的执行效果可能优于自定义着色器。如果您使用此效果足以引起性能问题,则可能值得尝试。