UWP クリップボードのGetBitmapAsyncで透過PNGが消える原因と回避策|StorageItemsでアルファ保持

Web上の透過PNGをコピーしてUWPアプリで貼り付け保存すると、DataPackageView.GetBitmapAsync()経由ではアルファが消えてしまうことがあります。これはUWPの不具合ではなく、クリップボードのビットマップ形式が透過を保持できないためです。本記事では原因の整理と、StorageItemsとしてPNGを受け取って透過を守る実装例を解説します。

目次

現象:GetBitmapAsync()で保存した画像から透過が消える

たとえばWebブラウザー(Microsoft Edge / Google Chromeなど)で透過PNG画像を右クリックし「画像をコピー」を実行し、UWPアプリ側でクリップボードから画像を取り出してそのままファイルへ書き出すと、背景が白や黒で塗りつぶされたように見えることがあります。見た目としては「透過がないPNG」になってしまい、合成やUI表示で困ります。

再現しやすい手順

  1. ブラウザーで透過PNG画像(背景がチェック柄で表示される画像など)を表示する
  2. 画像をコピーする(右クリック→画像をコピー、またはコピー操作)
  3. UWPアプリでクリップボードから DataPackageView.GetBitmapAsync() を使って画像を取得する
  4. 取得したストリームをそのままローカルへ保存し、保存したファイルを確認する

さらに確認を進めると、Windowsのクリップボード履歴(Windowsロゴキー + V)を開いた段階ですでに透過が失われている場合があります。つまり、アプリが取り出す以前に「ビットマップ表現」へ変換された時点でアルファ情報が落ちている可能性が高いです。

よくある“素朴な実装”

次のように GetBitmapAsync() → OpenReadAsync() → CopyAsync() で保存していると、見た目が変わる問題に遭遇しやすいです(拡張子をpngにしていても、中身はPNGとは限りません)。

var view = Windows.ApplicationModel.DataTransfer.Clipboard.GetContent();

if (view.Contains(Windows.ApplicationModel.DataTransfer.StandardDataFormats.Bitmap))
{
var bitmapRef = await view.GetBitmapAsync();
using (var input = await bitmapRef.OpenReadAsync())
{
var file = await Windows.Storage.ApplicationData.Current.LocalFolder
.CreateFileAsync("Hello1.png", Windows.Storage.CreationCollisionOption.ReplaceExisting);


    using (var output = await file.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite))
    {
        await Windows.Storage.Streams.RandomAccessStream.CopyAsync(input, output);
    }
}


}
症状見え方開発側で起きがちな誤解
透過が失われる背景が白/黒、または不透明でベタ塗り「保存処理やエンコードが悪いのでは?」
ファイル拡張子と中身が一致しない環境によっては開ける/開けないが変わる「.pngだからPNGのはず」

結論:不具合ではなく“仕様(by design)”として起きる

ポイントは、GetBitmapAsync() が取り出すのは「PNGそのもの」ではなく、クリップボードに格納された“ビットマップ表現(Bitmap)”だという点です。クリップボードは複数形式を同時に持てますが、ビットマップ形式は古典的な互換性重視の形式で、コピー元(ブラウザーなど)が提供する表現や、Windows側での変換の結果としてアルファ(透過)を保持できない形式で渡されることがあります。

この状態でいくらアプリ側で「PNGとして保存」しようとしても、元データにアルファが存在しないため、後から透過だけを復元することはできません。一度Bitmapとして落ちた時点で、透過情報が消えているのが本質です。

「Win+Vですでに透過が消える」ことがある理由

クリップボード履歴に表示されるサムネイルは、Windowsが扱いやすい形式(多くのアプリと互換性のある画像表現)で生成されます。コピー元が透過PNGを持っていても、その場で互換形式へ変換されると、アルファが落ちた状態で履歴に残ることがあります。ここで透過が消えているなら、UWPアプリが何をしても復元はできません。

クリップボードで起きていることを整理する

コピー元が透過PNGを持っていたとしても、クリップボードには次のような“別表現”が同時に入ることがあります。UWPでStandardDataFormats.Bitmapを要求すると、透過を持たない表現が選ばれてしまうケースがあります。

クリップボード上の表現透過(アルファ)UWPでの代表的な取得方法特徴
Bitmap(互換性の高い画像表現)保持できない/落ちやすいGetBitmapAsync()アプリ間互換のために用意されがち。透過が必要な用途には不向き。
PNGファイル(StorageItems)保持できるGetStorageItemsAsync()“ファイル”として受け取れれば、元のPNGのまま扱える。
HTML(imgタグなど)元のURL次第GetHtmlFormatAsync() など解析が必要。参照先がリモートなら別途ダウンロード処理が必要。

まずやるべき確認:AvailableFormatsで「何が入っているか」を見える化する

透過を保てるかどうかは、コピー元がどの形式を提供しているかに強く依存します。最初にDataPackageView.AvailableFormatsをログ出力して、クリップボードに何が入っているかを確認しておくと、原因の切り分けが速くなります。

var view = Windows.ApplicationModel.DataTransfer.Clipboard.GetContent();

foreach (var format in view.AvailableFormats)
{
    System.Diagnostics.Debug.WriteLine(format);
}

ここで StandardDataFormats.StorageItems が見えているなら、透過を保つルートが期待できます。逆に、Bitmap しか見当たらない場合は、アプリ側がいくら頑張っても透過復元はできません(コピー元/OSの段階で落ちているため)。

回避策:Bitmapではなく「PNGファイル(StorageItems)」として受け取る

透過PNGを確実に扱いたいなら、優先順位を次のように設計します。

優先順位取得方法狙い
最優先StandardDataFormats.StorageItemsPNGファイルそのもの(またはそれに準ずるデータ)として受け取り、アルファを保持する
次点コピー元が提供するPNG系の独自形式(存在する場合)アプリ/ブラウザーによってはPNGストリームが入ることがあるため
最後StandardDataFormats.Bitmap透過が不要な場合のフォールバック。必要なら別UIで注意喚起する

クリップボードからStorageItemsを取り出して、そのまま保存する例

コピー元がPNGを“ファイル”としても渡してくれる場合、StorageFileとして取り出してバイナリコピーするだけで、透過を含めてそのまま保存できます。ファイル形式の変換をしないため、画質劣化やアルファ欠落のリスクも最小です。

using System;
using System.Linq;
using Windows.ApplicationModel.DataTransfer;
using Windows.Storage;
using Windows.Storage.Streams;

var view = Clipboard.GetContent();

if (view.Contains(StandardDataFormats.StorageItems))
{
    var items = await view.GetStorageItemsAsync();
    var file = items
        .OfType<StorageFile>()
        .FirstOrDefault(f => string.Equals(f.FileType, ".png", StringComparison.OrdinalIgnoreCase));

    if (file != null)
    {
        // 例:アプリのローカルフォルダーへコピーして保存
        var dest = await ApplicationData.Current.LocalFolder
            .CreateFileAsync("Hello1.png", CreationCollisionOption.GenerateUniqueName);

        using (IRandomAccessStream input = await file.OpenReadAsync())
        using (IRandomAccessStream output = await dest.OpenAsync(FileAccessMode.ReadWrite))
        {
            await RandomAccessStream.CopyAsync(input, output);
        }
    }
}

ポイントは、“ビットマップに変換しない”ことです。PNGのまま受け取り、PNGのまま保存するルートを最初に探すのが最短で確実な回避策になります。

保存先をユーザーに選ばせる(FileSavePicker)

検証ではローカルフォルダー保存で十分ですが、実運用ではユーザーが保存先を選べるほうが便利です。UWPでは任意パスに自由に書けないため、FileSavePicker を使うのが定番です。

using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;

async System.Threading.Tasks.Task SaveToPickedFileAsync(StorageFile sourcePng)
{
    var picker = new FileSavePicker();
    picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
    picker.SuggestedFileName = "Image";
    picker.FileTypeChoices.Add("PNG", new[] { ".png" });

    var dest = await picker.PickSaveFileAsync();
    if (dest == null) return;

    using (IRandomAccessStream input = await sourcePng.OpenReadAsync())
    using (IRandomAccessStream output = await dest.OpenAsync(FileAccessMode.ReadWrite))
    {
        await RandomAccessStream.CopyAsync(input, output);
    }
}

なお、Picturesライブラリへ直接書き込む設計にする場合は、アプリの機能と配布形態に応じて必要な権限(機能/ケイパビリティ)の整理も忘れないようにしてください。ピッカー経由で保存する限りは、ユーザー操作が明示されるため運用しやすいです。

ドラッグ&ドロップでStorageItemsを受け取る例(透過を守りやすい)

コピーよりもドラッグ&ドロップのほうが、ブラウザーがファイル相当(StorageItems)を提供してくれる確率が上がりやすく、透過保持の成功率も上げやすいです。UIとして「ここに画像をドロップしてください」と誘導するのも現実的な対策です。

(XAML側の例)

<Grid AllowDrop="True"
      DragOver="OnDragOver"
      Drop="OnDrop">
    <TextBlock Text="画像をここにドロップ"
               HorizontalAlignment="Center"
               VerticalAlignment="Center"/>
</Grid>

(コードビハインド側の例)

using System;
using System.Linq;
using Windows.ApplicationModel.DataTransfer;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Input;

private void OnDragOver(object sender, DragEventArgs e)
{
if (e.DataView.Contains(StandardDataFormats.StorageItems))
{
e.AcceptedOperation = DataPackageOperation.Copy;
}
}

private async void OnDrop(object sender, DragEventArgs e)
{
if (!e.DataView.Contains(StandardDataFormats.StorageItems))
{
return;
}


var items = await e.DataView.GetStorageItemsAsync();
var file = items
    .OfType<StorageFile>()
    .FirstOrDefault(f => string.Equals(f.FileType, ".png", StringComparison.OrdinalIgnoreCase));

if (file == null)
{
    return;
}

var dest = await ApplicationData.Current.LocalFolder
    .CreateFileAsync("Dropped.png", CreationCollisionOption.GenerateUniqueName);

using (IRandomAccessStream input = await file.OpenReadAsync())
using (IRandomAccessStream output = await dest.OpenAsync(FileAccessMode.ReadWrite))
{
    await RandomAccessStream.CopyAsync(input, output);
}


}

補足:GetBitmapAsyncのストリームを「PNGだと思い込まない」

GetBitmapAsync()から得られるストリームは、必ずしもPNGとは限りません。保存時に拡張子だけpngにしてしまうと、ファイルの中身と拡張子が一致せず、ビューアーやライブラリによって挙動が変わります。透過が消えたように見えるケースでも、実際には「BMP相当のデータをpngとして保存している」ことが混ざり得ます。

先頭バイト(シグネチャ)で形式を判定する

問題の切り分けとして、ストリームの先頭バイトを見て「本当にPNGなのか」を判定すると手早いです。PNGとBMPでは先頭が大きく違うため、デバッグ時の目安になります。

形式先頭の目印メモ
PNG16進:89 50 4E 47 0D 0A 1A 0Aいわゆる「PNGシグネチャ」。透過を保持可能。
BMPASCII:BM(16進:42 4D)互換性重視。透過は落ちやすい。

簡易的に読む例は次の通りです(読み取った後はストリーム位置を戻してからデコードしてください)。

using Windows.Storage.Streams;

async System.Threading.Tasks.Task ReadHeaderAsync(IRandomAccessStream stream, uint bytes)
{
stream.Seek(0);
var reader = new DataReader(stream.GetInputStreamAt(0));
await reader.LoadAsync(bytes);


var buffer = new byte[bytes];
reader.ReadBytes(buffer);
return buffer;


}

Bitmapをフォールバックで使うなら、デコードしてから明示的にPNGへエンコードする

透過が不要なフォールバックとしてBitmapを使う場合でも、次のようにデコードしてから明示的にPNGへエンコードし直すほうが安全です(ただし、元にアルファが無ければ透過は復元できません)。

using Windows.ApplicationModel.DataTransfer;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Streams;

var view = Clipboard.GetContent();

if (view.Contains(StandardDataFormats.Bitmap))
{
    var bitmapRef = await view.GetBitmapAsync();

    using (var input = await bitmapRef.OpenReadAsync())
    {
        var decoder = await BitmapDecoder.CreateAsync(input);
        var pixelData = await decoder.GetPixelDataAsync();
        var pixels = pixelData.DetachPixelData();

        var file = await ApplicationData.Current.LocalFolder
            .CreateFileAsync("Fallback.png", CreationCollisionOption.ReplaceExisting);

        using (var output = await file.OpenAsync(FileAccessMode.ReadWrite))
        {
            var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, output);

            // decoder.BitmapAlphaMode が Ignore になっている時点で、すでにアルファが落ちています
            encoder.SetPixelData(
                decoder.BitmapPixelFormat,
                decoder.BitmapAlphaMode,
                decoder.PixelWidth,
                decoder.PixelHeight,
                decoder.DpiX,
                decoder.DpiY,
                pixels);

            await encoder.FlushAsync();
        }
    }
}
やりたいこと推奨注意点
透過PNGをそのまま保存したいStorageItemsでPNGを受け取り、バイナリコピーコピー元がStorageItemsを提供しない場合は成立しない
とにかく画像として保存できればよいBitmapをデコード→任意形式へ再エンコード透過は復元不可。拡張子と中身の不一致に注意

実務で効く運用面の対策:UIと導線で“透過が落ちないルート”へ誘導する

技術的な回避策(StorageItems優先)を入れても、コピー元がBitmapしか提供しない状況は残ります。その場合、アプリのUX側で次のような導線を用意しておくと、問い合わせや混乱が減ります。

  • 貼り付け時に AvailableFormats を見て、StorageItemsが無いなら「透過を保持できない可能性」を説明する
  • 「コピーではなくドラッグ&ドロップ」を推奨するUI(ドロップ領域、説明文、ツールチップ)を置く
  • 透過が必須の処理(合成、マスク、ステッカー生成など)では、Bitmapフォールバックを黙って通さず、ユーザーに選ばせる
アプリ機能透過が重要かおすすめの受け取り方メッセージ設計
スタンプ合成 / 透過前提の素材管理高いStorageItems(ドラッグ&ドロップ推奨)StorageItemsが無い場合は明確に注意し、別ルートを案内
メモ用途の画像貼り付け低いBitmapでも可透過が不要ならそのまま保存、必要なら設定で案内
サムネイル生成中可能ならStorageItems、無ければBitmap「元画像と見た目が変わる可能性」を補足

チェックリスト:透過PNGを落とさず扱うための設計ポイント

チェック項目狙い実装/運用のヒント
取得はStorageItemsを最優先にしているかPNGをPNGのまま扱う貼り付け/ドロップ双方で Contains(StorageItems) を先に見る
Bitmapは最後のフォールバックになっているか透過が落ちる前提で扱う透過必須機能では警告や導線を用意する
拡張子と中身の不一致を避けているか保存後の互換性を担保Bitmapはデコード→再エンコードし、形式に合う拡張子で保存する
“透過を後で戻せない”ことを理解しているか無駄な調査を減らすWin+Vでクリップボード側の見え方を確認し、早期に原因を切り分ける

まとめ:透過が必要なら「Bitmap取得」ではなく「ファイルとして取得」へ

  • DataPackageView.GetBitmapAsync()で透過が消えるのは、UWPの不具合というよりクリップボードのBitmap表現の制約として起きやすい
  • 透過PNGを守りたい場合は、StandardDataFormats.StorageItemsを優先し、StorageFileとしてPNGを開いて保存・表示する
  • 一度Bitmapとして落ちたデータから、後でアルファだけ復元することはできないため、設計段階で“透過が落ちない導線”を用意する

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次