Web上の透過PNGをコピーしてUWPアプリで貼り付け保存すると、DataPackageView.GetBitmapAsync()経由ではアルファが消えてしまうことがあります。これはUWPの不具合ではなく、クリップボードのビットマップ形式が透過を保持できないためです。本記事では原因の整理と、StorageItemsとしてPNGを受け取って透過を守る実装例を解説します。
現象:GetBitmapAsync()で保存した画像から透過が消える
たとえばWebブラウザー(Microsoft Edge / Google Chromeなど)で透過PNG画像を右クリックし「画像をコピー」を実行し、UWPアプリ側でクリップボードから画像を取り出してそのままファイルへ書き出すと、背景が白や黒で塗りつぶされたように見えることがあります。見た目としては「透過がないPNG」になってしまい、合成やUI表示で困ります。
再現しやすい手順
- ブラウザーで透過PNG画像(背景がチェック柄で表示される画像など)を表示する
- 画像をコピーする(右クリック→画像をコピー、またはコピー操作)
- UWPアプリでクリップボードから
DataPackageView.GetBitmapAsync()を使って画像を取得する - 取得したストリームをそのままローカルへ保存し、保存したファイルを確認する
さらに確認を進めると、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.StorageItems | PNGファイルそのもの(またはそれに準ずるデータ)として受け取り、アルファを保持する |
| 次点 | コピー元が提供する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では先頭が大きく違うため、デバッグ時の目安になります。
| 形式 | 先頭の目印 | メモ |
|---|---|---|
| PNG | 16進:89 50 4E 47 0D 0A 1A 0A | いわゆる「PNGシグネチャ」。透過を保持可能。 |
| BMP | ASCII: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として落ちたデータから、後でアルファだけ復元することはできないため、設計段階で“透過が落ちない導線”を用意する

コメント