Condividi tramite


Creare, modificare e salvare immagini bitmap

In questo articolo viene illustrato come caricare e salvare i file immagine utilizzando BitmapDecoder e BitmapEncoder e come utilizzare 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 una rappresentazione SoftwareBitmap di un VideoFrame.

  • FaceDetector consente di rilevare i visi in un SoftwareBitmap.

Il codice di esempio in questo articolo usa le API degli spazi dei nomi seguenti.

using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.Graphics.Imaging;
using Windows.UI.Xaml.Media.Imaging;

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 immagine. In questo esempio viene usato un oggetto FileOpenPicker per consentire all'utente di selezionare un file immagine.

FileOpenPicker fileOpenPicker = new FileOpenPicker();
fileOpenPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
fileOpenPicker.FileTypeFilter.Add(".jpg");
fileOpenPicker.ViewMode = PickerViewMode.Thumbnail;

var inputFile = await fileOpenPicker.PickSingleFileAsync();

if (inputFile == null)
{
    // The user cancelled the picking operation
    return;
}

Chiamare il metodo OpenAsync di StorageFile per ottenere un flusso di accesso casuale contenente i dati 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.

SoftwareBitmap softwareBitmap;

using (IRandomAccessStream stream = await inputFile.OpenAsync(FileAccessMode.Read))
{
    // Create the decoder from the stream
    BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);

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

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. In questo esempio viene usato un oggetto FileSavePicker per consentire all'utente di selezionare un file in uscita.

FileSavePicker fileSavePicker = new FileSavePicker();
fileSavePicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
fileSavePicker.FileTypeChoices.Add("JPEG files", new List<string>() { ".jpg" });
fileSavePicker.SuggestedFileName = "image";

var outputFile = await fileSavePicker.PickSaveFileAsync();

if (outputFile == null)
{
    // The user cancelled the picking operation
    return;
}

Chiamare il metodo OpenAsync di 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 void SaveSoftwareBitmapToFile(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, if needed
        encoder.BitmapTransform.ScaledWidth = 320;
        encoder.BitmapTransform.ScaledHeight = 240;
        encoder.BitmapTransform.Rotation = Windows.Graphics.Imaging.BitmapRotation.Clockwise90Degrees;
        encoder.BitmapTransform.InterpolationMode = BitmapInterpolationMode.Fant;
        encoder.IsThumbnailGenerated = true;

        try
        {
            await encoder.FlushAsync();
        }
        catch (Exception err)
        {
            const int WINCODEC_ERR_UNSUPPORTEDOPERATION = unchecked((int)0x88982F81);
            switch (err.HResult)
            {
                case WINCODEC_ERR_UNSUPPORTEDOPERATION: 
                    // If the encoder does not support writing a thumbnail, then try again
                    // but disable thumbnail generation.
                    encoder.IsThumbnailGenerated = false;
                    break;
                default:
                    throw;
            }
        }

        if (encoder.IsThumbnailGenerated == false)
        {
            await encoder.FlushAsync();
        }


    }
}

È possibile specificare opzioni di codifica aggiuntive quando si crea BitmapEncoder creando un nuovo oggetto BitmapPropertySet e popolandolo con uno o più oggetti BitmapTypedValue che rappresentano le impostazioni del codificatore. Per un elenco delle opzioni del codificatore supportate, vedere Informazioni di riferimento sulle opzioni bitmapEncoder.

var propertySet = new Windows.Graphics.Imaging.BitmapPropertySet();
var qualityValue = new Windows.Graphics.Imaging.BitmapTypedValue(
    1.0, // Maximum quality
    Windows.Foundation.PropertyType.Single
    );

propertySet.Add("ImageQuality", qualityValue);

await Windows.Graphics.Imaging.BitmapEncoder.CreateAsync(
    Windows.Graphics.Imaging.BitmapEncoder.JpegEncoderId,
    stream,
    propertySet
);

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"/>

Attualmente, il controllo Immagine supporta solo immagini che usano la codifica BGRA8 e pre-moltiplicato o nessun canale alfa. Prima di tentare di visualizzare un'immagine, verificare che abbia il formato corretto e, in caso contrario, usare il metodo SoftwareBitmap statico Convert per convertire l'immagine nel formato supportato.

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

if (softwareBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8 ||
    softwareBitmap.BitmapAlphaMode == BitmapAlphaMode.Straight)
{
    softwareBitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
}

var source = new SoftwareBitmapSource();
await source.SetBitmapAsync(softwareBitmap);

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

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

Creare un Oggetto SoftwareBitmap da WriteableBitmap

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

SoftwareBitmap outputBitmap = SoftwareBitmap.CreateCopyFromBuffer(
    writeableBitmap.PixelBuffer,
    BitmapPixelFormat.Bgra8,
    writeableBitmap.PixelWidth,
    writeableBitmap.PixelHeight
);

Creare o modificare un SoftwareBitmap a livello di codice

Finora questo argomento ha affrontato l'uso dei file 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.

SoftwareBitmap usa l'interoperabilità COM per esporre il buffer non elaborato contenente i dati pixel.

Per usare l'interoperabilità COM, è necessario includere un riferimento allo spazio dei nomi System.Runtime.InteropServices nel progetto.

using System.Runtime.InteropServices;

Inizializzare l'interfaccia COM IMemoryBufferByteAccess aggiungendo il codice seguente all'interno dello spazio dei nomi.

[ComImport]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe interface IMemoryBufferByteAccess
{
    void GetBuffer(out byte* buffer, out uint capacity);
}

Creare un nuovo SoftwareBitmap con formato pixel e dimensioni desiderate. In alternativa, usa un SoftwareBitmap esistente per il quale vuoi modificare i dati pixel. Chiamare SoftwareBitmap.LockBuffer per ottenere un'istanza della classe BitmapBuffer che rappresenta il buffer di dati pixel. Eseguire il cast di BitmapBuffer nell'interfaccia COM IMemoryBufferByteAccess e quindi chiamare IMemoryBufferByteAccess.GetBuffer per compilare una matrice di byte con dati. Utilizzare il metodo BitmapBuffer.GetPlaneDescription per ottenere un oggetto BitmapPlaneDescription che consente di calcolare l'offset nel buffer per ogni pixel.

softwareBitmap = new SoftwareBitmap(BitmapPixelFormat.Bgra8, 100, 100, BitmapAlphaMode.Premultiplied);

using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Write))
{
    using (var reference = buffer.CreateReference())
    {
        byte* dataInBytes;
        uint capacity;
        ((IMemoryBufferByteAccess)reference).GetBuffer(out dataInBytes, out capacity);

        // Fill-in the BGRA plane
        BitmapPlaneDescription bufferLayout = buffer.GetPlaneDescription(0);
        for (int i = 0; i < bufferLayout.Height; i++)
        {
            for (int j = 0; j < bufferLayout.Width; j++)
            {

                byte value = (byte)((float)j / bufferLayout.Width * 255);
                dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 0] = value;
                dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 1] = value;
                dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 2] = value;
                dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 3] = (byte)255;
            }
        }
    }
}

Poiché questo metodo accede al buffer non elaborato sottostante ai tipi di Windows Runtime, deve essere dichiarato usando la parola chiave unsafe. È inoltre necessario configurare il progetto in Microsoft Visual Studio per consentire la compilazione di codice non sicuro aprendo la pagina Proprietà del progetto, facendo clic sulla pagina proprietà Build e selezionando la casella di spunta Allow Unsafe Code.

Creare un SoftwareBitmap da una superficie Direct3D

Per creare un oggetto SoftwareBitmap da una superficie Direct3D, è necessario includere lo spazio dei nomi Windows.Graphics.DirectX.Direct3D11 nel progetto.

using Windows.Graphics.DirectX.Direct3D11;

Chiamare 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 void CreateSoftwareBitmapFromSurface(IDirect3DSurface surface)
{
    softwareBitmap = await SoftwareBitmap.CreateCopyFromSurfaceAsync(surface);
}

Convertire un oggetto SoftwareBitmap in un formato 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.

SoftwareBitmap bitmapBGRA8 = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);

Transcodifica un file immagine

È possibile transcodificare un file 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 oggetto InMemoryRandomAccessStream per l'encoder in cui scrivere e chiamare 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 nell'encoder verranno scritte nel file di output invariato. 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 void TranscodeImageFile(StorageFile imageFile)
{


    using (IRandomAccessStream fileStream = await imageFile.OpenAsync(FileAccessMode.ReadWrite))
    {
        BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream);

        var memStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
        BitmapEncoder encoder = await BitmapEncoder.CreateForTranscodingAsync(memStream, decoder);

        encoder.BitmapTransform.ScaledWidth = 320;
        encoder.BitmapTransform.ScaledHeight = 240;

        await encoder.FlushAsync();

        memStream.Seek(0);
        fileStream.Seek(0);
        fileStream.Size = 0;
        await RandomAccessStream.CopyAsync(memStream, fileStream);

        memStream.Dispose();
    }
}