Vytváření, úpravy a ukládání rastrových obrázků

Tento článek vysvětluje, jak načíst a uložit soubory obrázků pomocí BitmapDecoder a BitmapEncoder a jak používat objekt SoftwareBitmap k reprezentaci rastrových obrázků.

SoftwareBitmap třída je všestranné rozhraní API, které lze vytvořit z více zdrojů, včetně souborů obrázků, WriteableBitmap objektů, Direct3D povrchů a kódu. SoftwareBitmap umožňuje snadno převádět mezi různými pixelovými formáty a alfa režimy a umožňuje přístup k datům pixelů na nízké úrovni. SoftwareBitmap je také společné rozhraní používané několika funkcemi Windows, mezi které patří:

  • CapturedFrame umožňuje získat snímky zachycené fotoaparátem jako SoftwareBitmap.

  • VideoFrame umožňuje získat SoftwareBitmap reprezentaci VideoFrame.

  • FaceDetector umožňuje rozpoznat tváře v SoftwareBitmap.

Vzorový kód v tomto článku používá rozhraní API z následujících oborů názvů.

using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.Graphics.Imaging;
using Microsoft.UI.Xaml.Media.Imaging;
using System.Runtime.InteropServices.WindowsRuntime;

Vytvoření mapy SoftwareBitmap ze souboru obrázku pomocí BitmapDecoder

Pokud chcete ze souboru vytvořit SoftwareBitmap, získejte instanci StorageFile obsahující data obrázku. Tento příklad používá FileOpenPicker, aby uživatel mohl vybrat soubor obrázku.

private async Task<StorageFile?> PickInputFileAsync()
{
    var picker = new FileOpenPicker();

    // Initialize the picker with the window handle (required for WinUI 3).
    var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow);
    WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd);

    picker.ViewMode = PickerViewMode.Thumbnail;
    picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
    picker.FileTypeFilter.Add(".jpg");
    picker.FileTypeFilter.Add(".jpeg");
    picker.FileTypeFilter.Add(".png");
    picker.FileTypeFilter.Add(".bmp");

    return await picker.PickSingleFileAsync();
}

Zavolejte metodu OpenAsync objektu StorageFile, abyste získali stream s náhodným přístupem obsahující data obrázku. Voláním statické metody BitmapDecoder.CreateAsync získáte instanci třídy BitmapDecoder pro zadaný datový proud. Voláním GetSoftwareBitmapAsync získáte objekt SoftwareBitmap obsahující obrázek.

private async Task<SoftwareBitmap> CreateSoftwareBitmapFromFileAsync(StorageFile file)
{
    using IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.Read);

    // Create a decoder from the image file.
    BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);

    // Get the SoftwareBitmap representation of the file.
    SoftwareBitmap softwareBitmap = await decoder.GetSoftwareBitmapAsync();

    return softwareBitmap;
}

Uložení mapy SoftwareBitmap do souboru pomocí BitmapEncoder

Chcete-li uložit SoftwareBitmap do souboru, získejte instanci StorageFile , do které bude image uložena. Tento příklad používá FileSavePicker, aby uživatel mohl vybrat výstupní soubor.

private async Task<StorageFile?> PickOutputFileAsync()
{
    var picker = new FileSavePicker();

    // Initialize the picker with the window handle (required for WinUI 3).
    var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow);
    WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd);

    picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
    picker.SuggestedFileName = "output";
    picker.FileTypeChoices.Add("JPEG Image", new List<string> { ".jpg" });
    picker.FileTypeChoices.Add("PNG Image", new List<string> { ".png" });

    return await picker.PickSaveFileAsync();
}

Zavolejte metodu OpenAsync objektu StorageFile, abyste získali datový proud s náhodným přístupem, do kterého bude obrázek zapsán. Zavolejte statickou metodu BitmapEncoder.CreateAsync, abyste získali instanci třídy BitmapEncoder pro zadaný datový proud. První parametr createAsync je identifikátor GUID představující kodek, který se má použít k kódování obrázku. BitmapEncoder třída zveřejňuje vlastnost obsahující ID pro každý kodek podporovaný kodérem, například JpegEncoderId.

K nastavení obrázku, který bude kódován, použijte metodu SetSoftwareBitmap . Můžete nastavit hodnoty vlastnosti BitmapTransform pro použití základních transformací na obrázek při kódování. IsThumbnailGenerated vlastnost určuje, zda je miniatura generována kodérem. Všimněte si, že ne všechny formáty souborů podporují miniatury, takže pokud tuto funkci používáte, měli byste zachytit nepodporovanou chybu operace, která se vyvolá, pokud nejsou miniatury podporovány.

Volání FlushAsync způsobí, že kodér zapíše data obrázku do zadaného souboru.

private async Task SaveSoftwareBitmapToFileAsync(SoftwareBitmap softwareBitmap, StorageFile outputFile)
{
    using IRandomAccessStream stream = await outputFile.OpenAsync(FileAccessMode.ReadWrite);

    // Create an encoder with the desired format.
    BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream);

    // Set the software bitmap.
    encoder.SetSoftwareBitmap(softwareBitmap);

    // Set additional encoding parameters (optional).
    encoder.BitmapTransform.ScaledWidth = (uint)softwareBitmap.PixelWidth;
    encoder.BitmapTransform.ScaledHeight = (uint)softwareBitmap.PixelHeight;
    encoder.BitmapTransform.InterpolationMode = BitmapInterpolationMode.Fant;
    encoder.IsThumbnailGenerated = true;

    try
    {
        await encoder.FlushAsync();
    }
    catch (Exception ex)
    {
        const int WINCODEC_ERR_UNSUPPORTEDOPERATION = unchecked((int)0x88982F81);
        switch (ex.HResult)
        {
            case WINCODEC_ERR_UNSUPPORTEDOPERATION:
                // If the encoder does not support thumbnail generation,
                // disable it and try again.
                encoder.IsThumbnailGenerated = false;
                break;

            default:
                throw;
        }
    }

    if (!encoder.IsThumbnailGenerated)
    {
        await encoder.FlushAsync();
    }
}

Při vytváření objektu BitmapEncoder můžete zadat další možnosti kódování tak, že vytvoříte nový objekt BitmapPropertySet a naplníte jej jedním nebo více objekty BitmapTypedValue, které představují nastavení kodéru. Seznam podporovaných možností kodéru naleznete v části Referenční informace k možnostem BitmapEncoderu.

private async Task SaveWithEncodingOptionsAsync(SoftwareBitmap softwareBitmap, StorageFile outputFile)
{
    using IRandomAccessStream stream = await outputFile.OpenAsync(FileAccessMode.ReadWrite);

    // Create encoding options with a specific image quality.
    var propertySet = new BitmapPropertySet();
    var qualityValue = new BitmapTypedValue(0.9, Windows.Foundation.PropertyType.Single);
    propertySet.Add("ImageQuality", qualityValue);

    // Create the encoder with the encoding options.
    BitmapEncoder encoder = await BitmapEncoder.CreateAsync(
        BitmapEncoder.JpegEncoderId, stream, propertySet);

    encoder.SetSoftwareBitmap(softwareBitmap);
    await encoder.FlushAsync();
}

Použití objektu SoftwareBitmap s ovládacím prvkem Image v XAML

Pokud chcete zobrazit obrázek na stránce XAML pomocí ovládacího prvku Obrázek , nejprve na stránce XAML definujte ovládací prvek Obrázek .

<Image x:Name="imageControl"
       Grid.Row="2"
       Stretch="Uniform"/>

Ovládací prvek Obrázek v současné době podporuje pouze obrázky, které používají kódování BGRA8 a předem vynásobené nebo bez alfa kanálu. Před pokusem o zobrazení obrázku otestujte, zda má správný formát, a pokud ne, použijte metodu Static ConvertSoftwareBitmap k převodu obrázku do podporovaného formátu.

Vytvořte nový objekt SoftwareBitmapSource . Nastavte obsah zdrojového objektu voláním SetBitmapAsync a předáním SoftwareBitmap. Potom můžete vlastnost Source ovládacího prvku Image nastavit na nově vytvořený SoftwareBitmapSource.

private async Task DisplaySoftwareBitmapAsync(SoftwareBitmap softwareBitmap)
{
    // SoftwareBitmap must be Bgra8 with premultiplied or no alpha
    // to display in a XAML Image control.
    if (softwareBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8 ||
        softwareBitmap.BitmapAlphaMode == BitmapAlphaMode.Straight)
    {
        softwareBitmap = SoftwareBitmap.Convert(
            softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
    }

    // In WinUI 3, SoftwareBitmapSource is in Microsoft.UI.Xaml.Media.Imaging.
    var source = new SoftwareBitmapSource();
    await source.SetBitmapAsync(softwareBitmap);

    // Set the source of the Image control.
    imageControl.Source = source;
}

SoftwareBitmapSource můžete také použít k nastavení SoftwareBitmap jako ImageSource pro ImageBrush.

Vytvoření objektu SoftwareBitmap z writeableBitmap

SoftwareBitmap můžete vytvořit z existující WriteableBitmapvoláním SoftwareBitmap.CreateCopyFromBuffer a zadáním PixelBuffer vlastnosti WriteableBitmap nastavit pixelová data. Druhý argument umožňuje zadat formát pixelů pro nově vytvořený SoftwareBitmap. Pomocí vlastností PixelWidth a PixelHeightobjektu WriteableBitmap můžete určit rozměry nového obrázku.

private SoftwareBitmap ConvertWriteableBitmapToSoftwareBitmap(WriteableBitmap writeableBitmap)
{
    // Create a SoftwareBitmap from the WriteableBitmap's pixel buffer.
    SoftwareBitmap softwareBitmap = SoftwareBitmap.CreateCopyFromBuffer(
        writeableBitmap.PixelBuffer,
        BitmapPixelFormat.Bgra8,
        writeableBitmap.PixelWidth,
        writeableBitmap.PixelHeight);

    return softwareBitmap;
}

Programové vytvoření nebo úprava objektu SoftwareBitmap

Zatím toto téma vyřešilo práci se soubory obrázků. Nový SoftwareBitmap můžete také vytvořit programově v kódu a použít stejnou techniku pro přístup k pixelovým datům softwaru SoftwareBitmap a jejich úpravu.

Metoda CopyFromBuffer slouží k naplnění objektu SoftwareBitmap z bajtového pole a CopyToBuffer ke zkopírování pixelových dat do bajtového pole pro čtení nebo úpravy. Pokud chcete použít metodu rozšíření AsBuffer k zabalení pole bajtů do objektu IBuffer, zahrňte obor názvů System.Runtime.InteropServices.WindowsRuntime (ten je součástí direktiv SnippetNamespaces using na začátku tohoto článku).

Vytvořte novou mapu SoftwareBitmap s požadovaným formátem pixelů a velikostí. Přidělte bajtové pole dostatečně velké k uložení dat v pixelech, vyplňte je požadovanými hodnotami a potom zavolejte CopyFromBuffer pro zápis dat do rastrového obrázku.

private SoftwareBitmap CreateGradientBitmap(int width, int height)
{
    // Create a new SoftwareBitmap programmatically.
    var softwareBitmap = new SoftwareBitmap(
        BitmapPixelFormat.Bgra8, width, height, BitmapAlphaMode.Premultiplied);

    // Allocate a byte array for the pixel data.
    int bytesPerPixel = 4; // BGRA8
    byte[] pixelData = new byte[width * height * bytesPerPixel];

    // Fill the bitmap with a gradient pattern.
    for (int row = 0; row < height; row++)
    {
        for (int col = 0; col < width; col++)
        {
            int pixelIndex = (row * width + col) * bytesPerPixel;

            // Blue channel: gradient left to right.
            pixelData[pixelIndex + 0] = (byte)((double)col / width * 255);
            // Green channel: gradient top to bottom.
            pixelData[pixelIndex + 1] = (byte)((double)row / height * 255);
            // Red channel: inverse diagonal gradient.
            pixelData[pixelIndex + 2] = (byte)(255 - (((double)(col + row)
                / (width + height)) * 255));
            // Alpha channel: fully opaque.
            pixelData[pixelIndex + 3] = 255;
        }
    }

    // Copy the pixel data into the SoftwareBitmap.
    softwareBitmap.CopyFromBuffer(pixelData.AsBuffer());

    return softwareBitmap;
}

Vytvořte SoftwareBitmap z povrchu Direct3D

Pokud chcete vytvořit objekt SoftwareBitmap z povrchu Direct3D, musíte zahrnout Windows. Graphics.DirectX.Direct3D11 obor názvů v projektu.

using Windows.Graphics.DirectX.Direct3D11;

Voláním CreateCopyFromSurfaceAsync vytvoříte nový SoftwareBitmap z povrchu. Jak název označuje, nový SoftwareBitmap má samostatnou kopii dat obrázku. Úpravy objektu SoftwareBitmap nebudou mít žádný vliv na povrch Direct3D.

private async Task<SoftwareBitmap> CreateBitmapFromSurfaceAsync(IDirect3DSurface surface)
{
    // Create a SoftwareBitmap from a Direct3D surface.
    SoftwareBitmap softwareBitmap =
        await SoftwareBitmap.CreateCopyFromSurfaceAsync(surface);

    return softwareBitmap;
}

Převod mapy SoftwareBitmap do jiného formátu pixelů

SoftwareBitmap třída poskytuje statickou metodu Convert, která umožňuje snadno vytvořit nový SoftwareBitmap, který používá pixelový formát a alfa režim, který zadáte z existující SoftwareBitmap. Všimněte si, že nově vytvořený rastrový obrázek má samostatnou kopii dat obrázku. Změny nového rastrového obrázku nebudou mít vliv na zdrojový rastrový obrázek.

private SoftwareBitmap ConvertBitmapPixelFormat(SoftwareBitmap softwareBitmap)
{
    // Convert the pixel format and alpha mode of the SoftwareBitmap.
    SoftwareBitmap convertedBitmap = SoftwareBitmap.Convert(
        softwareBitmap,
        BitmapPixelFormat.Bgra8,
        BitmapAlphaMode.Premultiplied);

    return convertedBitmap;
}

Transkódování souboru obrázku

Soubor obrázku můžete překódovat přímo z BitmapDecoder do BitmapEncoder. Vytvořte IRandomAccessStream ze souboru, který se má překódovat. Vytvořte nový BitmapDecoder ze vstupního datového proudu. Vytvořte nový InMemoryRandomAccessStream, do kterého bude kodér zapisovat, a zavolejte BitmapEncoder.CreateForTranscodingAsync; předejte datový proud v paměti a objekt dekodéru. Při překódování nejsou podporovány možnosti kódování; místo toho byste měli použít CreateAsync. Všechny vlastnosti ve vstupním souboru obrázku, které v kodéru nenastavíte, se zapíšou do výstupního souboru beze změny. Zavolejte FlushAsync, aby kodér zakódoval data do proudu v paměti. Nakonec vyhledejte datový proud souboru a datový proud v paměti na začátek a zavolejte CopyAsync pro zápis datového proudu v paměti do datového proudu souboru.

private async Task TranscodeImageFileAsync(StorageFile inputFile, StorageFile outputFile)
{
    using IRandomAccessStream inputStream =
        await inputFile.OpenAsync(FileAccessMode.Read);
    using IRandomAccessStream outputStream =
        await outputFile.OpenAsync(FileAccessMode.ReadWrite);

    // Create a decoder for the input file.
    BitmapDecoder decoder = await BitmapDecoder.CreateAsync(inputStream);

    // Create an encoder for transcoding to the output file.
    BitmapEncoder encoder =
        await BitmapEncoder.CreateForTranscodingAsync(outputStream, decoder);

    // Optionally apply transforms during transcoding.
    encoder.BitmapTransform.ScaledWidth = decoder.PixelWidth / 2;
    encoder.BitmapTransform.ScaledHeight = decoder.PixelHeight / 2;
    encoder.BitmapTransform.InterpolationMode = BitmapInterpolationMode.Fant;

    await encoder.FlushAsync();
}