Creare, modificare e salvare immagini bitmap

Questo articolo illustra come caricare e salvare file di immagine usando BitmapDecoder e BitmapEncoder e come usare l'oggetto SoftwareBitmap per rappresentare le immagini bitmap.

La classe SoftwareBitmap è un'API versatile che può essere creata da più origini, inclusi file di immagine, oggetti WriteableBitmap , superfici Direct3D e codice. SoftwareBitmap consente di eseguire facilmente la conversione tra formati pixel diversi e modalità alfa e consente l'accesso di basso livello ai dati pixel. Inoltre, SoftwareBitmap è un'interfaccia comune usata da più funzionalità di Windows, tra cui:

  • CapturedFrame consente di acquisire fotogrammi dalla fotocamera come SoftwareBitmap.

  • VideoFrame consente di ottenere un SoftwareBitmap rappresentazione di un VideoFrame.

  • FaceDetector consente di rilevare i visi in un SoftwareBitmap.

Il codice di esempio in questo articolo utilizza le API dei namespace seguenti.

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;

Creare un SoftwareBitmap da un file di immagine con BitmapDecoder

Per creare un SoftwareBitmap da un file, ottenere un'istanza di StorageFile contenente i dati dell'immagine. Questo esempio usa un FileOpenPicker per consentire all'utente di selezionare un file di immagine.

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();
}

Chiamare il metodo OpenAsync dell'oggetto StorageFile per ottenere un flusso di accesso casuale contenente i dati dell'immagine. Chiamare il metodo statico BitmapDecoder.CreateAsync per ottenere un'istanza della classe BitmapDecoder per il flusso specificato. Chiamare GetSoftwareBitmapAsync per ottenere un oggetto SoftwareBitmap contenente l'immagine.

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;
}

Salvare un SoftwareBitmap in un file con BitmapEncoder

Per salvare un SoftwareBitmap in un file, ottenere un'istanza di StorageFile in cui verrà salvata l'immagine. Questo esempio usa un FileSavePicker per consentire all'utente di selezionare un file di output.

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();
}

Chiamare il metodo OpenAsync dell'oggetto StorageFile per ottenere un flusso di accesso casuale a cui verrà scritta l'immagine. Chiamare il metodo statico BitmapEncoder.CreateAsync per ottenere un'istanza della classe BitmapEncoder per il flusso specificato. Il primo parametro di CreateAsync è un GUID che rappresenta il codec da usare per codificare l'immagine. La classe BitmapEncoder espone una proprietà contenente l'ID per ogni codec supportato dal codificatore, ad esempio JpegEncoderId.

Utilizzare il metodo SetSoftwareBitmap per impostare l'immagine che verrà codificata. È possibile impostare i valori della proprietà BitmapTransform per applicare trasformazioni di base all'immagine durante la codifica. La proprietà IsThumbnailGenerated determina se un'anteprima viene generata dal codificatore. Si noti che non tutti i formati di file supportano le anteprime, quindi se si usa questa funzionalità, è consigliabile rilevare l'errore di operazione non supportata che verrà generata se le anteprime non sono supportate.

Chiamare FlushAsync per fare in modo che il codificatore scriva i dati dell'immagine nel file specificato.

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();
    }
}

È possibile specificare opzioni di codifica aggiuntive quando si crea il BitmapEncoder creando un nuovo BitmapPropertySet Oggetto > e popolarlo con uno o più oggetti BitmapTypedValue che rappresentano le impostazioni del codificatore. Per l'elenco delle opzioni del codificatore supportate, consulta le informazioni di riferimento per le opzioni di BitmapEncoder.

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();
}

Usare SoftwareBitmap con un controllo Immagine XAML

Per visualizzare un'immagine all'interno di una pagina XAML usando il controllo Immagine , definire prima un controllo Immagine nella pagina XAML.

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

Attualmente, il controllo Image supporta solo immagini in formato BGRA8 e con un canale alfa premoltiplicato oppure senza canale alfa. Prima di tentare di visualizzare un'immagine, verificare che abbia il formato corretto e, in caso contrario, usare il metodo Convert statico SoftwareBitmap per convertire l'immagine nel formato supportato.

Creare un nuovo oggetto SoftwareBitmapSource . Imposta il contenuto dell'oggetto di origine chiamando SetBitmapAsync, passando un SoftwareBitmap. È quindi possibile impostare la proprietà Source del controllo Image sul softwareBitmapSource appena creato.

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;
}

È anche possibile usare SoftwareBitmapSource per impostare softwareBitmap come ImageSource per un oggetto ImageBrush.

Creare un SoftwareBitmap da un WriteableBitmap

È possibile creare un Oggetto SoftwareBitmap da un oggetto WriteableBitmap esistente chiamando SoftwareBitmap.CreateCopyFromBuffer e fornendo la proprietà PixelBuffer di WriteableBitmap per impostare i dati pixel. Il secondo argomento consente di specificare il formato pixel per il softwareBitmap appena creato. Puoi usare le proprietà PixelWidth e PixelHeight di WriteableBitmap per specificare le dimensioni della nuova immagine.

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;
}

Creare o modificare un SoftwareBitmap a livello di codice

Finora questo argomento ha affrontato l'uso dei file di immagine. È anche possibile creare un nuovo SoftwareBitmap a livello di codice e usare la stessa tecnica per accedere e modificare i dati pixel di SoftwareBitmap.

Utilizzare il metodo CopyFromBuffer per riempire un SoftwareBitmap da un array di byte e CopyToBuffer per copiare i dati dei pixel in un array di byte per la lettura o la modifica. Per usare il metodo di estensione AsBuffer per eseguire il wrapping di una matrice di byte in un oggetto IBuffer, includere lo spazio dei nomi System.Runtime.InteropServices.WindowsRuntime (questo spazio dei nomi è incluso nelle istruzioni using SnippetNamespaces all'inizio di questo articolo).

Creare un nuovo SoftwareBitmap con il formato pixel e le dimensioni desiderati. Allocare una matrice di byte sufficientemente grande per contenere i dati pixel, riempirli con i valori desiderati e quindi chiamare CopyFromBuffer per scrivere i dati nella bitmap.

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;
}

Creare un oggetto SoftwareBitmap da una superficie Direct3D

Per creare un oggetto SoftwareBitmap a partire da una superficie Direct3D, devi includere lo spazio dei nomi Windows.Graphics.DirectX.Direct3D11 nel tuo progetto.

using Windows.Graphics.DirectX.Direct3D11;

Chiama CreateCopyFromSurfaceAsync per creare un nuovo SoftwareBitmap dalla superficie. Come indica il nome, il nuovo SoftwareBitmap ha una copia separata dei dati dell'immagine. Le modifiche apportate a SoftwareBitmap non avranno alcun effetto sulla superficie Direct3D.

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

    return softwareBitmap;
}

Convertire un SoftwareBitmap in un formato di pixel diverso

La classe SoftwareBitmap fornisce il metodo statico Convert, che consente di creare facilmente un nuovo SoftwareBitmap che usa il formato pixel e la modalità alfa specificati da un SoftwareBitmap esistente. Si noti che la bitmap appena creata ha una copia separata dei dati dell'immagine. Le modifiche apportate alla nuova bitmap non influiscono sulla bitmap di origine.

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;
}

Transcodifica un file di immagine

È possibile transcodificare un file di immagine direttamente da un BitmapDecoder a un BitmapEncoder. Creare un IRandomAccessStream dal file da transcodificare. Creare un nuovo BitmapDecoder dal flusso di input. Creare un nuovo InMemoryRandomAccessStream affinché il codificatore scriva e chiami BitmapEncoder.CreateForTranscodingAsync, passando il flusso in memoria e l'oggetto decodificatore. Le opzioni di codifica non sono supportate durante la transcodifica; È invece consigliabile usare CreateAsync. Tutte le proprietà nel file di immagine di input non impostate specificamente nel codificatore verranno scritte nel file di output invariate. Chiamare FlushAsync per fare in modo che il codificatore venga codificato nel flusso in memoria. Infine, cercare il flusso di file e il flusso in memoria all'inizio e chiamare CopyAsync per scrivere il flusso in memoria nel flusso di file.

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();
}