午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

アルファ値を失わずに画像のコピペできた、.NET WPFのClipboard

f:id:gogowaten:20210210125532p:plain
半透明画像のコピペ結果
Clipboardクラスにはクリップボードに画像をコピーするSetImageと、クリップボードから画像を取得するGetImageがあるけど、これを使うとピクセルのアルファ値が255(完全不透明)か、0(完全透明)に変換されてしまう


f:id:gogowaten:20210210144012p:plain
テストに使った画像
左半分が完全不透明、右半分が半透明

C# クリップボード 画像アルファ値
とかで検索して見つかる
クリップボードを用いるとBMPデータの透過色が変換されてしまう https://social.msdn.microsoft.com/Forums/vstudio/ja-JP/0416af2a-a2f4-49b5-a085-f117f8576ddf/bmp?forum=vcgeneralja

[VB] クリップボードに送った透過画像が貼り付けると透過にならない。: ~Road To Beautiful Life ~ https://tomovertex.at.webry.info/201009/article_2.html

この辺を見るとアルファ値を失わずにコピペするには、
コピー
画像をPNG形式にエンコードしたものをMemoryStreamにSave(書き込む?)して、それをClipboardのSetDataで"PNG"を指定してコピーする

ペースト(クリップボードから取り出し)
ClipboardのGetDataで"PNG"を指定して取り出す、これの型はMemoryStreamなので、BitmapFrameのCteateに渡せば画像として取り出せる

作成環境

画像をコピーするアプリ
MainWindow.xaml

<Window x:Class="_20210210_Clipboardへ画像コピー.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:_20210210_Clipboardへ画像コピー"
        mc:Ignorable="d"
        Title="MainWindow" Height="250" Width="400">
  <Grid UseLayoutRounding="True">
    <DockPanel>
      <Menu DockPanel.Dock="Top">
        <MenuItem Header="copy" Click="MenuItem_Click"/>
        <MenuItem Header="copy(PNG)" Click="MenuItem_Click_1"/>
      </Menu>
      <Image x:Name="MyImage" Stretch="None"/>
    </DockPanel>
  </Grid>
</Window>


MainWindow.xaml.cs

using System;
using System.Windows;
using System.Windows.Media.Imaging;

namespace _20210210_Clipboardへ画像コピー
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private BitmapSource MyBitmapSource;
        public MainWindow()
        {
            InitializeComponent();

            //アプリに埋め込んだ画像ファイルを取り出す
            string name = "_20210210_Clipboardへ画像コピー.不透明と半透明.png";
            var assembly = System.Reflection.Assembly.GetExecutingAssembly();
            using var stream = assembly.GetManifestResourceStream(name);
            if (stream != null) MyBitmapSource = BitmapFrame.Create(stream);
            
            MyImage.Source = MyBitmapSource;
        }        

        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            //普通にセット
            Clipboard.SetImage(MyBitmapSource);
        }

        private void MenuItem_Click_1(object sender, RoutedEventArgs e)
        {
            //PNG形式でセット
            ClipboardToPngImage(MyBitmapSource);
        }
        private void ClipboardToPngImage(BitmapSource source)
        {
            //画像をPNGにエンコード
            PngBitmapEncoder pngEnc = new();
            pngEnc.Frames.Add(BitmapFrame.Create(source));
            //エンコードした画像をMemoryStreamにSava
            using var ms = new System.IO.MemoryStream();
            pngEnc.Save(ms);
            //MemoryStreamをクリップボードにコピー
            Clipboard.SetData("PNG", ms);
        }
    }
}



クリップボードの画像を貼り付ける(取り出す)アプリ
MainWindow.xaml

<Window x:Class="_20210210_ClipboardのPNG形式画像を貼り付け.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:_20210210_ClipboardのPNG形式画像を貼り付け"
        mc:Ignorable="d"
        Title="MainWindow" Height="250" Width="400">
  <Grid UseLayoutRounding="True">
    <DockPanel>
      <Menu DockPanel.Dock="Top">
        <MenuItem Header="paste" Click="MenuItem_Click"/>
      </Menu>
      <Image x:Name="MyImage" Stretch="None"/>
    </DockPanel>
  </Grid>
</Window>


MainWindow.xaml.cs

using System.Windows;
using System.Windows.Media.Imaging;

namespace _20210210_ClipboardのPNG形式画像を貼り付け
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            BitmapSource source = GetImageFromClipboardWithPNG();
            if (source == null)
            {
                MessageBox.Show("クリップボードに画像はなかった");
            }
            else
            {
                MyImage.Source = source;
            }

        }

        /// <summary>
        /// クリップボードからBitmapSourceを取り出して返す、PNG(アルファ値保持)形式に対応
        /// </summary>
        /// <returns></returns>
        private BitmapSource GetImageFromClipboardWithPNG()
        {
            BitmapSource source = null;
            //クリップボードにPNG形式のデータがあったら、それを使ってBitmapFrame作成して返す
            //なければ普通にClipboardのGetImage、それでもなければnullを返す
            using var ms = (System.IO.MemoryStream)Clipboard.GetData("PNG");
            if (ms != null)
            {
                //source = BitmapFrame.Create(ms);//これだと取得できない
                source = BitmapFrame.Create(ms, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);                
            }
            else if (Clipboard.ContainsImage())
            {
                source = Clipboard.GetImage();
            }
            return source;
        }
    }
}



2021WPF/20210210_Clipboardへ画像コピー at master · gogowaten/2021WPF · GitHub

2021WPF/20210210_ClipboardのPNG形式画像を貼り付け at master · gogowaten/2021WPF · GitHub



それぞれのコピー形式でPaint.NETに貼り付けてみると

f:id:gogowaten:20210210135204p:plain
Paint.NETに貼り付け
Paint.NETはクリップボードPNG形式に対応しているので、アルファ値が正しく表示される

PNG形式にして、MemoryStreamにして、それをクリップボードにコピーするのはいいとして、受け取るほうがPNG形式のMemoryStreamに対応していないと、画像として取り出せないので貼り付けることができない
対応しているアプリは

  • Paint.NET
  • FireAlpaca
  • Pixtack紫陽花

などがあった、最近のアプリなら対応している感じ

あと今思ったのは、コピーするときにビットマップ形式と今回のPNG形式の両方をコピーすることはできる?

できた↓

追記ここから
ビットマップ形式とPNG形式の両方コピーするのできた

クリップボードに複数の形式のデータをコピーする - .NET Tips(VB.NET, C#...)
https://dobon.net/vb/dotnet/system/clipboardmultidata.html
ここを見て

github.com



MainWindow.xaml

<Window x:Class="_20210210_ClipboardにSetImageとPNG形式.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:_20210210_ClipboardにSetImageとPNG形式"
        mc:Ignorable="d"
        Title="MainWindow" Height="250" Width="300">
  <Grid>
    <DockPanel>
      <Menu DockPanel.Dock="Top">
        <MenuItem Header="コピー" Click="MenuItem_Click"/>
      </Menu>
      <Image x:Name="MyImage" StretchDirection="DownOnly"/>
    </DockPanel>
  </Grid>
</Window>



MainWindow.xaml.cs

using System.Windows;
using System.Windows.Media.Imaging;

namespace _20210210_ClipboardにSetImageとPNG形式
{
    public partial class MainWindow : Window
    {
        BitmapSource MyBitmapSource;

        public MainWindow()
        {
            InitializeComponent();

            //アプリに埋め込んだ画像ファイルを取り出す
            string name = "_20210210_ClipboardにSetImageとPNG形式.不透明と半透明.png";
            var assembly = System.Reflection.Assembly.GetExecutingAssembly();
            using var stream = assembly.GetManifestResourceStream(name);
            if (stream != null) MyBitmapSource = BitmapFrame.Create(stream);

            MyImage.Source = MyBitmapSource;
        }

        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            ClipboardSetImageWithPng(MyBitmapSource);
        }

        //        クリップボードに複数の形式のデータをコピーする - .NET Tips(VB.NET, C#...)
        //https://dobon.net/vb/dotnet/system/clipboardmultidata.html

        /// <summary>
        /// BitmapSourceをPNG形式に変換したものと、そのままの形式の両方をクリップボードにコピーする
        /// </summary>
        /// <param name="source"></param>
        private void ClipboardSetImageWithPng(BitmapSource source)
        {
            //DataObjectに入れたいデータを入れて、それをクリップボードにセットする
            DataObject data = new();

            //BitmapSource形式そのままでセット
            data.SetData(typeof(BitmapSource), source);

            //PNG形式にエンコードしたものをMemoryStreamして、それをセット
            //画像をPNGにエンコード
            PngBitmapEncoder pngEnc = new();
            pngEnc.Frames.Add(BitmapFrame.Create(source));
            //エンコードした画像をMemoryStreamにSava
            using var ms = new System.IO.MemoryStream();
            pngEnc.Save(ms);
            data.SetData("PNG", ms);

            //クリップボードにセット
            Clipboard.SetDataObject(data, true);

        }

    }
}



DataObjectにBitmapSourceそのままと、PNGに変換してMemoryStreamにしたのを両方入れて、それをクリップボードにSetDataObject

あとは受け取り側のアプリ問題で、クリップボードPNG形式に対応したアプリならPNG形式を優先して取り出すはずだし、対応していないアプリならビットマップ形式の方を取り出すはず

テスト
アプリ起動して

f:id:gogowaten:20210210151027p:plain
テストアプリ
コピーボタンでクリップボードにビットマップ形式とPNG形式両方がコピーされるので、画像アプリに貼り付けてみる

FireAlpaca

f:id:gogowaten:20210210151213p:plain
FireAlpacaに貼り付け
ファイル→クリップボードから新規作成を実行すると

f:id:gogowaten:20210210151311p:plain
貼り付け後
PNG形式の方で貼り付けられたようで、右半分が半透明になっている、いいね!

Pixtack紫陽花

f:id:gogowaten:20210210151511p:plain
Pixtack紫陽花に貼り付け
クリップボードから追加ボタンで

f:id:gogowaten:20210210151554p:plain
貼り付けた
これも半透明になっている、追加で貼り付けて

f:id:gogowaten:20210210151716p:plain
追加で貼り付けて重ねたところ
2つの画像を重ねてみると半透明になっているのがわかる

クリップボードPNG形式画像に対応していないアプリの場合
ペイント

f:id:gogowaten:20210210151904p:plain
ペイントに貼り付け
メニューのクリップボード→貼り付けを実行すると

f:id:gogowaten:20210210151941p:plain
貼り付けたところ
半透明が失われている画像が貼りけられたので、これはPNG形式はパスされて、残ったビットマップ形式の画像が貼り付けられたことになる、いいね!

ビットマップ形式優先ボタン

f:id:gogowaten:20210210152451p:plain
ビットマップ形式で貼り付け
Pixtack紫陽花にはビットマップ形式を選んで貼り付けるボタンもある、これで貼り付けるとPNGよりビットマップ形式が優先されて貼り付けられた
このボタンはエクセルのグラフを貼り付ける用で作ったんだよねえ



関連記事
次回は翌日

gogowaten.hatenablog.com

前回のWPF記事は昨日

このアプリにつけたcopy(PNG)が今回の方法

8ヶ月後


1年以上前


今回のを利用した