影像是現代應用程式不可或缺的元素。Windows 8 提供豐富的影像 API:Image 控制項基礎顯示、九宮格 (NineGrid) 保護邊框不變形、WriteableBitmap 像素級操作、ImageProperties 讀寫中繼資料、BitmapDecoder/Encoder/Transform 進行縮放、旋轉、格式轉換。本文將逐一實作演示。
支援三種 URI 配置:ms-appx:/// (應用程式封裝)、ms-appdata:///local|roaming|temp/ (應用程式資料)、http(s):// (網路)。程式碼可透過 BitmapImage 直接指定 URI,或開啟串流後 SetSourceAsync。
<Page x:Class="XamlDemo.Image.Display" xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:XamlDemo.Image" xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid Background="Transparent"> <StackPanel Margin="120 0 0 0" Orientation="Horizontal" VerticalAlignment="Top"> <Border BorderBrush="Red" BorderThickness="1" Width="200" Height="100" Margin="20 0 0 0"> <Image Source="/Assets/Logo.png" Stretch="Uniform" Width="200" Height="100" /> </Border> <Border BorderBrush="Red" BorderThickness="1" Width="200" Height="100" Margin="20 0 0 0"> <Image Source="ms-appx:///Assets/Logo.png" Stretch="Uniform" Width="200" Height="100" /> </Border> <Border BorderBrush="Red" BorderThickness="1" Width="200" Height="100" Margin="20 0 0 0"> <Image x:Name="img" Stretch="Uniform" Width="200" Height="100" /> </Border> <Border BorderBrush="Red" BorderThickness="1" Width="200" Height="100" Margin="20 0 0 0"> <Image x:Name="img2" Stretch="Uniform" Width="200" Height="100" /> </Border> </StackPanel> </Grid> </Page>
using System;
using Windows.Storage.Streams;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Navigation;
namespace XamlDemo.Image
{
public sealed partial class Display : Page
{
public Display()
{
this.InitializeComponent();
}
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
img.Source = new BitmapImage(new Uri("ms-appx:///Assets/Logo.png", UriKind.Absolute));
RandomAccessStreamReference imageStreamRef = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///Assets/Logo.png", UriKind.Absolute));
IRandomAccessStream imageStream = await imageStreamRef.OpenReadAsync();
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.SetSource(imageStream);
img2.Source = bitmapImage;
}
}
}
NineGrid 屬性接受 Thickness (左、上、右、下),定義四條分割線,保護邊框區域不被拉伸,類似 9-slice scaling。範例使用 16x16 的邊框圖片,設定 NineGrid="1 1 1 1" 後放大至 200x200 仍保持邊框銳利。
<Page x:Class="XamlDemo.Image.NineGrid" xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:XamlDemo.Image" xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid Background="Transparent"> <StackPanel Margin="120 0 0 0" Orientation="Horizontal" VerticalAlignment="Top"> <Image Source="/Assets/NineGrid/Demo.png" Width="200" Height="200" /> <Image Source="/Assets/NineGrid/Demo.png" Width="200" Height="200" NineGrid="1 1 1 1" Margin="20 0 0 0" /> </StackPanel> </Grid> </Page>
WriteableBitmap 使用 BGRA 格式 (每像素 4 位元組)。三大核心場景:
SetSourceAsync。PixelDataProvider -> 取得位元組陣列 -> 修改 -> 寫入 PixelBuffer.AsStream() -> Invalidate() 重繪。Invalidate()。<Page x:Class="XamlDemo.Image.WriteableBitmapDemo" xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:XamlDemo.Image" xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid Background="Transparent"> <StackPanel Margin="120 0 0 0"> <Image x:Name="img" Width="300" Height="300" HorizontalAlignment="Left" /> <Button x:Name="btnLoadImage" Content="load image" Margin="0 10 0 0" Click="btnLoadImage_Click_1" /> <Button x:Name="btnChangePixel" Content="載入一個圖片並修改其中的像素的顏色值" Margin="0 10 0 0" Click="btnChangePixel_Click_1" /> <Button x:Name="btnCreatePixel" Content="建立一個圖片,設定其每個像素的顏色值" Margin="0 10 0 0" Click="btnCreatePixel_Click_1" /> </StackPanel> </Grid> </Page>
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
using System;
using System.IO;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Streams;
namespace XamlDemo.Image
{
public sealed partial class WriteableBitmapDemo : Page
{
public WriteableBitmapDemo()
{
this.InitializeComponent();
}
private async void btnLoadImage_Click_1(object sender, RoutedEventArgs e)
{
WriteableBitmap writeableBitmap = new WriteableBitmap(300, 300);
img.Source = writeableBitmap;
StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/Logo.png"));
using (IRandomAccessStream fileStream = await file.OpenAsync(FileAccessMode.Read))
{
await writeableBitmap.SetSourceAsync(fileStream);
}
}
private async void btnChangePixel_Click_1(object sender, RoutedEventArgs e)
{
WriteableBitmap writeableBitmap = new WriteableBitmap(300, 300);
img.Source = writeableBitmap;
StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/Logo.png"));
using (IRandomAccessStream fileStream = await file.OpenAsync(FileAccessMode.Read))
{
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream);
BitmapTransform transform = new BitmapTransform()
{
ScaledWidth = Convert.ToUInt32(writeableBitmap.PixelWidth),
ScaledHeight = Convert.ToUInt32(writeableBitmap.PixelHeight)
};
PixelDataProvider pixelData = await decoder.GetPixelDataAsync(
BitmapPixelFormat.Bgra8, BitmapAlphaMode.Straight, transform,
ExifOrientationMode.IgnoreExifOrientation, ColorManagementMode.DoNotColorManage);
byte[] sourcePixels = pixelData.DetachPixelData();
for (int i = 0; i < sourcePixels.Length; i++)
{
sourcePixels[i] -= 10;
}
using (Stream stream = writeableBitmap.PixelBuffer.AsStream())
{
await stream.WriteAsync(sourcePixels, 0, sourcePixels.Length);
}
}
writeableBitmap.Invalidate();
}
private async void btnCreatePixel_Click_1(object sender, RoutedEventArgs e)
{
WriteableBitmap writeableBitmap = new WriteableBitmap(300, 300);
img.Source = writeableBitmap;
Random random = new Random();
byte[] result = new byte[300 * 300 * 4];
for (int i = 0; i < result.Length; )
{
result[i++] = (byte)random.Next(0, 256);
result[i++] = (byte)random.Next(0, 256);
result[i++] = (byte)random.Next(0, 256);
result[i++] = 255;
}
using (Stream stream = writeableBitmap.PixelBuffer.AsStream())
{
await stream.WriteAsync(result, 0, result.Length);
}
writeableBitmap.Invalidate();
}
}
}
StorageFile.Properties.GetImagePropertiesAsync() 取得 ImageProperties,可讀寫標題、關鍵字、寬度、高度、拍攝時間、GPS、相機廠牌等 EXIF/XMP 資訊。修改後呼叫 SavePropertiesAsync() 持久化。
using System;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.Storage.FileProperties;
using Windows.Storage.Pickers;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
using XamlDemo.Common;
namespace XamlDemo.Image
{
public sealed partial class ImageProperty : Page
{
public ImageProperty()
{
this.InitializeComponent();
}
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
if (Helper.EnsureUnsnapped())
{
FileOpenPicker picker = new FileOpenPicker();
picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.PicturesLibrary;
picker.FileTypeFilter.Add(".jpg");
picker.FileTypeFilter.Add(".png");
StorageFile file = await picker.PickSingleFileAsync();
if (file != null)
{
ImageProperties imageProperties = await GetImageProperty(file);
UpdateImageProperty(imageProperties);
}
}
}
private async Task<ImageProperties> GetImageProperty(StorageFile file)
{
ImageProperties imageProperties = await file.Properties.GetImagePropertiesAsync();
lblMsg.Text = "title: " + imageProperties.Title;
lblMsg.Text += Environment.NewLine;
lblMsg.Text += "keywords: " + string.Join(",", imageProperties.Keywords);
lblMsg.Text += Environment.NewLine;
lblMsg.Text += "width: " + imageProperties.Width;
lblMsg.Text += Environment.NewLine;
lblMsg.Text += "height: " + imageProperties.Height;
lblMsg.Text += Environment.NewLine;
return imageProperties;
}
private async void UpdateImageProperty(ImageProperties imageProperties)
{
Random random = new Random();
imageProperties.Title = random.Next(0, 1000).ToString();
imageProperties.Keywords.Clear();
imageProperties.Keywords.Add(random.Next(0, 1000).ToString());
imageProperties.Keywords.Add(random.Next(0, 1000).ToString());
try
{
await imageProperties.SavePropertiesAsync();
lblMsg.Text += "title 和 keywords 已被修改,重新進來可看效果";
}
catch (Exception ex)
{
lblMsg.Text = ex.ToString();
}
}
}
}
流程:BitmapDecoder.CreateAsync 解碼來源 -> BitmapTransform 設定目標寬高、旋轉角度 -> GetPixelDataAsync 取得轉換後像素 -> BitmapEncoder.CreateAsync 指定目標格式 (PNG/JPEG/JPEG-XR/GIF/TIFF) -> SetPixelData 寫入像素 -> FlushAsync 完成。範例將 JPG 縮放為 100x100、旋轉 180 度、轉存為 PNG。
<Page x:Class="XamlDemo.Image.ImageTransform" xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:XamlDemo.Image" xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid Background="Transparent"> <StackPanel Margin="120 0 0 0"> <Image x:Name="imgOriginal" Stretch="Uniform" Width="300" Height="200" HorizontalAlignment="Left" /> <Image x:Name="imgTransformed" Stretch="Uniform" Width="300" Height="200" HorizontalAlignment="Left" Margin="0 10 0 0" /> </StackPanel> </Grid> </Page>
using System;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Navigation;
using XamlDemo.Common;
namespace XamlDemo.Image
{
public sealed partial class ImageTransform : Page
{
public ImageTransform()
{
this.InitializeComponent();
}
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
if (Helper.EnsureUnsnapped())
{
FileOpenPicker picker = new FileOpenPicker();
picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.PicturesLibrary;
picker.FileTypeFilter.Add(".jpg");
StorageFile fileRead = await picker.PickSingleFileAsync();
if (fileRead != null)
{
BitmapImage src = new BitmapImage();
src.SetSource(await fileRead.OpenAsync(FileAccessMode.Read));
imgOriginal.Source = src;
StorageFile fileWrite = await ApplicationData.Current.TemporaryFolder.CreateFileAsync(@"webabcdTest\imageTransformDemo.png", CreationCollisionOption.ReplaceExisting);
using (IRandomAccessStream inputStream = await fileRead.OpenAsync(FileAccessMode.Read), outputStream = await fileWrite.OpenAsync(FileAccessMode.ReadWrite))
{
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(inputStream);
BitmapTransform transform = new BitmapTransform();
transform.ScaledWidth = 100;
transform.ScaledHeight = 100;
transform.Rotation = BitmapRotation.Clockwise180Degrees;
PixelDataProvider pixelProvider = await decoder.GetPixelDataAsync(
decoder.BitmapPixelFormat, decoder.BitmapAlphaMode, transform,
ExifOrientationMode.RespectExifOrientation, ColorManagementMode.ColorManageToSRgb);
byte[] pixels = pixelProvider.DetachPixelData();
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, outputStream);
encoder.SetPixelData(
decoder.BitmapPixelFormat, decoder.BitmapAlphaMode,
100, 100, decoder.DpiX, decoder.DpiY, pixels);
await encoder.FlushAsync();
}
imgTransformed.Source = new BitmapImage(new Uri("ms-appdata:///temp/webabcdTest/imageTransformDemo.png", UriKind.Absolute));
}
}
}
}
}
Image 控制項支援多種 URI 配置,靈活引用封裝、應用程式資料、網路圖片。NineGrid 是 UI 邊框圖片不變形的利器。WriteableBitmap 提供像素級完全控制,適合濾鏡、繪圖、動態生成圖片。ImageProperties 讓應用程式能讀寫豐富的影像中繼資料。BitmapDecoder/Transform/Encoder 管線化處理縮放、旋轉、格式轉換,效能高且保真度好。