Créer, modifier et enregistrer des images bitmap

Cet article explique comment charger et enregistrer des fichiers image à l’aide de BitmapDecoder et BitmapEncoder et comment utiliser l’objet SoftwareBitmap pour représenter des images bitmap.

La classe SoftwareBitmap est une API polyvalente qui peut être créée à partir de plusieurs sources, notamment des fichiers image, des objets WriteableBitmap, des surfaces Direct3D et du code. SoftwareBitmap vous permet de convertir facilement entre différents formats de pixels et modes alpha, et permet un accès de bas niveau aux données de pixels. En outre, SoftwareBitmap est une interface commune utilisée par plusieurs fonctionnalités de Windows, notamment :

  • CapturedFrame vous permet d’obtenir des images capturées par l’appareil photo en tant que SoftwareBitmap.

  • VideoFrame vous permet d’obtenir une représentation SoftwareBitmap d’un VideoFrame.

  • FaceDetector vous permet de détecter des visages dans un SoftwareBitmap.

L’exemple de code de cet article utilise des API à partir des espaces de noms suivants.

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

Créer un SoftwareBitmap à partir d’un fichier image avec BitmapDecoder

Pour créer un SoftwareBitmap à partir d’un fichier, obtenez une instance de StorageFile contenant les données d’image. Cet exemple utilise un FileOpenPicker pour permettre à l’utilisateur de sélectionner un fichier image.

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

Appelez la méthode OpenAsync de l’objet StorageFile pour obtenir un flux d’accès aléatoire contenant les données d’image. Appelez la méthode statique BitmapDecoder.CreateAsync pour obtenir une instance de la classe BitmapDecoder pour le flux spécifié. Appelez GetSoftwareBitmapAsync pour obtenir un objet SoftwareBitmap contenant l’image.

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

Enregistrer un SoftwareBitmap dans un fichier avec BitmapEncoder

Pour enregistrer un SoftwareBitmap dans un fichier, obtenez une instance de StorageFile dans laquelle l’image sera enregistrée. Cet exemple utilise un FileSavePicker pour permettre à l’utilisateur de sélectionner un fichier de sortie.

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

Appelez la méthode OpenAsync de l’objet StorageFile pour obtenir un flux d’accès aléatoire vers lequel l’image sera écrite. Appelez la méthode statique BitmapEncoder.CreateAsync pour obtenir une instance de la classe BitmapEncoder pour le flux spécifié. Le premier paramètre de CreateAsync est un GUID représentant le codec qui doit être utilisé pour encoder l’image. La classe BitmapEncoder expose une propriété contenant l’ID de chaque codec pris en charge par l’encodeur, tel que JpegEncoderId.

Utilisez la méthode SetSoftwareBitmap pour définir l’image qui sera encodée. Vous pouvez définir des valeurs de la propriété BitmapTransform pour appliquer des transformations de base à l’image pendant qu’elle est encodée. La propriété IsThumbnailGenerated détermine si une miniature est générée par l’encodeur. Notez que tous les formats de fichier ne prennent pas en charge les miniatures. Par conséquent, si vous utilisez cette fonctionnalité, vous devez intercepter l’erreur d’opération non prise en charge qui sera levée si les miniatures ne sont pas prises en charge.

Appelez FlushAsync pour que l’encodeur écrive les données d’image dans le fichier spécifié.

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


    }
}

Vous pouvez spécifier des options d’encodage supplémentaires lorsque vous créez le BitmapEncoder en créant un objet BitmapPropertySet et en le remplissant avec un ou plusieurs objets BitmapTypedValue représentant les paramètres d’encodeur. Pour obtenir la liste des options d’encodeur prises en charge, consultez la référence sur les options de 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
);

Utiliser SoftwareBitmap avec un contrôle d’image XAML

Pour afficher une image dans une page XAML à l’aide du contrôle d’image, commencez par définir un contrôle d’image dans votre page XAML.

<Image x:Name="imageControl"/>

Actuellement, le contrôle d’image prend uniquement en charge les images qui utilisent l’encodage BGRA8 et le canal alpha prémultiplié ou non. Avant d’essayer d’afficher une image, testez-la pour vous assurer qu’elle a le format correct et, si ce n’est pas le cas, utilisez la méthode SoftwareBitmap static Convert pour convertir l’image au format pris en charge.

Créez un objet SoftwareBitmapSource. Définissez le contenu de l’objet source en appelant SetBitmapAsync, en passant un SoftwareBitmap. Vous pouvez ensuite définir la propriété Source du contrôle d’image sur le logiciel SoftwareBitmapSource nouvellement créé.

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;

Vous pouvez également utiliser SoftwareBitmapSource pour définir un SoftwareBitmap comme ImageSource pour un ImageBrush.

Créer un SoftwareBitmap à partir d’un WriteableBitmap

Vous pouvez créer un SoftwareBitmap à partir d’un WriteableBitmap existant en appelant SoftwareBitmap.CreateCopyFromBuffer et en fournissant la propriété PixelBuffer du WriteableBitmap pour définir les données de pixel. Le deuxième argument vous permet de demander un format de pixel pour le WriteableBitmap nouvellement créé. Vous pouvez utiliser les propriétés PixelWidth et PixelHeight du WriteableBitmap pour spécifier les dimensions de la nouvelle image.

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

Créer ou modifier un SoftwareBitmap par programmation

Jusqu’à présent, cette rubrique a abordé l’utilisation des fichiers image. Vous pouvez également créer un logiciel SoftwareBitmap par programmation dans le code et utiliser la même technique pour accéder aux données de pixels de SoftwareBitmap et les modifier.

SoftwareBitmap utilise COM Interop pour exposer la mémoire tampon brute contenant les données de pixels.

Pour utiliser COM Interop, vous devez inclure une référence à l’espace de noms System.Runtime.InteropServices dans votre projet.

using System.Runtime.InteropServices;

Initialisez l’interface COM IMemoryBufferByteAccess en ajoutant le code suivant dans votre espace de noms.

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

Créez un SoftwareBitmap avec le format et la taille de pixels souhaités. Vous pouvez également utiliser un SoftwareBitmap existant pour lequel vous souhaitez modifier les données de pixels. Appelez SoftwareBitmap.LockBuffer pour obtenir une instance de la classe BitmapBuffer représentant la mémoire tampon de données de pixels. Castez le BitmapBuffer sur l’interface COM IMemoryBufferByteAccess, puis appelez IMemoryBufferByteAccess.GetBuffer pour remplir un tableau d’octets avec des données. Utilisez la méthode BitmapBuffer.GetPlaneDescription pour obtenir un objet BitmapPlaneDescription qui vous aidera à calculer le décalage dans la mémoire tampon pour chaque 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;
            }
        }
    }
}

Étant donné que cette méthode accède à la mémoire tampon brute sous-jacente aux types Windows Runtime, elle doit être déclarée à l’aide du mot clé unsafe. Vous devez également configurer votre projet dans Microsoft Visual Studio pour autoriser la compilation de code unsafe en ouvrant la page Propriétés du projet, en cliquant sur la page de propriétés Générer, puis en cochant la case Autoriser le code unsafe.

Créer un SoftwareBitmap à partir d’une surface Direct3D

Pour créer un objet SoftwareBitmap à partir d’une surface Direct3D, vous devez inclure l’espace de noms Windows.Graphics.DirectX.Direct3D11 dans votre projet.

using Windows.Graphics.DirectX.Direct3D11;

Appelez CreateCopyFromSurfaceAsync pour créer un SoftwareBitmap à partir de la surface. Comme le nom l’indique, le nouveau SoftwareBitmap a une copie distincte des données d’image. Les modifications apportées à SoftwareBitmap n’ont aucun effet sur la surface Direct3D.

private async void CreateSoftwareBitmapFromSurface(IDirect3DSurface surface)
{
    softwareBitmap = await SoftwareBitmap.CreateCopyFromSurfaceAsync(surface);
}

Convertir un SoftwareBitmap en un format de pixel différent

La classe SoftwareBitmap fournit la méthode statique, Convert, qui vous permet de créer facilement un SoftwareBitmap qui utilise le format pixel et le mode alpha que vous spécifiez à partir d’un SoftwareBitmap. Notez que le bitmap nouvellement créée comporte une copie distincte des données d’image. Les modifications apportées au nouveau bitmap n’affectent pas le bitmap source.

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

Transcoder un fichier image

Vous pouvez transcoder un fichier image directement à partir d’un BitmapDecoder vers un BitmapEncoder. Créez un IRandomAccessStream à partir du fichier à transcoder. Créez un BitmapDecoder à partir du flux d’entrée. Créez un InMemoryRandomAccessStream pour que l’encodeur écrive et appelle BitmapEncoder.CreateForTranscodingAsync, en passant le flux en mémoire et l’objet décodeur. Les options d’encodage ne sont pas prises en charge lors du transcodage ; au lieu de cela, vous devez utiliser CreateAsync. Toutes les propriétés du fichier image d’entrée que vous ne définissez pas spécifiquement sur l’encodeur seront écrites inchangées dans le fichier de sortie. Appelez FlushAsync pour que l’encodeur encode dans le flux en mémoire. Enfin, faites défiler le flux de fichiers et le flux en mémoire jusqu’au début et appelez CopyAsync pour écrire le flux en mémoire dans le flux de fichiers.

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