Freigeben über


Erstellen, Bearbeiten und Speichern von Bitmapbildern

Dieser Artikel erklärt, wie man Bilddateien mit BitmapDecoder und BitmapEncoder lädt und speichert und wie man das SoftwareBitmap-Objekt verwendet, um Bitmap-Bilder darzustellen.

Die SoftwareBitmap-Klasse ist eine vielseitige API, die aus mehreren Quellen erstellt werden kann, darunter Bilddateien, WriteableBitmap-Objekte, Direct3D-Oberflächen und Code. Mit SoftwareBitmap können Sie problemlos zwischen verschiedenen Pixelformaten und Alphamodi konvertieren und den Zugriff auf Pixeldaten auf niedriger Ebene ermöglichen. Darüber hinaus ist SoftwareBitmap eine gemeinsame Schnittstelle, die von mehreren Features von Windows verwendet wird, darunter:

  • CapturedFrame ermöglicht es Ihnen, Frames abzurufen, die von der Kamera als SoftwareBitmap aufgenommen werden.

  • Mit VideoFrame können Sie eine SoftwareBitmap-Darstellung eines VideoFrames abrufen.

  • FaceDetector ermöglicht es Ihnen, Gesichter in einer SoftwareBitmap zu erkennen.

Der Beispielcode in diesem Artikel verwendet APIs aus den folgenden Namespaces.

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

Erstellen einer SoftwareBitmap aus einer Bilddatei mit BitmapDecoder

Um eine SoftwareBitmap aus einer Datei zu erstellen, rufen Sie eine Instanz von StorageFile ab, die die Bilddaten enthält. In diesem Beispiel wird ein FileOpenPicker verwendet, damit der Benutzer eine Bilddatei auswählen kann.

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

Rufen Sie die OpenAsync-Methode des StorageFile-Objekts auf, um einen Datenstrom mit wahllosem Zugriff abzurufen, der die Bilddaten enthält. Rufen Sie die statische Methode BitmapDecoder.CreateAsync auf, um eine Instanz der BitmapDecoder-Klasse für den angegebenen Datenstrom abzurufen. Rufen Sie GetSoftwareBitmapAsync auf, um ein SoftwareBitmap-Objekt abzurufen, das das Bild enthält.

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

Speichern einer SoftwareBitmap in einer Datei mit BitmapEncoder

Um eine SoftwareBitmap in einer Datei zu speichern, rufen Sie eine Instanz von StorageFile ab, in der das Bild gespeichert wird. In diesem Beispiel wird ein FileSavePicker verwendet, um dem Benutzer die Auswahl einer Ausgabedatei zu ermöglichen.

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

Rufen Sie die OpenAsync-Methode des StorageFile-Objekts auf, um einen Datenstrom mit zufälligem Zugriff abzurufen, in den das Bild geschrieben wird. Rufen Sie die statische Methode BitmapEncoder.CreateAsync auf, um eine Instanz der BitmapEncoder-Klasse für den angegebenen Datenstrom abzurufen. Der erste Parameter für CreateAsync ist eine GUID, die den Codec darstellt, der zum Codieren des Bilds verwendet werden soll. BitmapEncoder-Klasse macht eine Eigenschaft verfügbar, die die ID für jeden Codec enthält, der vom Encoder unterstützt wird, z. B. JpegEncoderId.

Verwenden Sie die SetSoftwareBitmap-Methode, um das Bild festzulegen, das codiert wird. Sie können Werte der BitmapTransform-Eigenschaft festlegen, um grundlegende Transformationen auf das Bild anzuwenden, während es codiert wird. Die IsThumbnailGenerated-Eigenschaft bestimmt, ob eine Miniaturansicht vom Encoder generiert wird. Beachten Sie, dass nicht alle Dateiformate Miniaturansichten unterstützen. Wenn Sie dieses Feature verwenden, sollten Sie daher den Fehler beim nicht unterstützten Vorgang abfangen, der ausgelöst wird, wenn Miniaturansichten nicht unterstützt werden.

Rufen Sie FlushAsync auf, damit der Encoder die Bilddaten in die angegebene Datei schreibt.

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


    }
}

Sie können beim Erstellen des BitmapEncoder zusätzliche Codierungsoptionen angeben, indem Sie ein neues BitmapPropertySet-Objekt erstellen und es mit einem oder mehreren BitmapTypedValue-Objekten füllen, die die Encodereinstellungen darstellen. Eine Liste der unterstützten Encoderoptionen finden Sie in der Referenz zu BitmapEncoder-Optionen.

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

Verwenden von SoftwareBitmap mit einer XAML-Bildsteuerung

Wenn Sie ein Bild in einer XAML-Seite mithilfe des Bildsteuerelements anzeigen möchten, definieren Sie zuerst ein Bildsteuerelement auf der XAML-Seite.

<Image x:Name="imageControl"/>

Derzeit unterstützt das Bildsteuerelement nur Bilder, die BGRA8-Codierung und vorab multipliziert oder keinen Alphakanal verwenden. Bevor Sie versuchen, ein Bild anzuzeigen, testen Sie, ob es das richtige Format hat. Falls nicht, verwenden Sie die statische Methode SoftwareBitmap Convert, um das Bild in das unterstützte Format zu konvertieren.

Erstellen Sie ein neues SoftwareBitmapSource-Objekt. Legen Sie den Inhalt des Quellobjekts fest, indem Sie SetBitmapAsync aufrufen und eine SoftwareBitmap übergeben. Anschließend können Sie die Source-Eigenschaft des Image-Steuerelements auf die neu erstellte SoftwareBitmapSource festlegen.

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;

Sie können SoftwareBitmapSource auch verwenden, um eine SoftwareBitmap als ImageSource für einen ImageBrush festzulegen.

Erstellen einer SoftwareBitmap aus einer WriteableBitmap

Sie können eine SoftwareBitmap aus einer vorhandenen WriteableBitmap erstellen, indem Sie SoftwareBitmap.CreateCopyFromBuffer aufrufen und die PixelBuffer-Eigenschaft der WriteableBitmap bereitstellen, um die Pixeldaten festzulegen. Mit dem zweiten Argument können Sie ein Pixelformat für das neu erstellte WriteableBitmap anfordern. Mit den PixelWidth - und PixelHeight-Eigenschaften der WriteableBitmap können Sie die Abmessungen des neuen Bilds angeben.

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

Programmgesteuertes Erstellen oder Bearbeiten einer SoftwareBitmap

Bisher wurde in diesem Thema die Arbeit mit Bilddateien behandelt. Sie können auch programmgesteuert eine neue SoftwareBitmap im Code erstellen und dieselbe Technik verwenden, um auf die Pixeldaten von SoftwareBitmap zuzugreifen und sie zu ändern.

SoftwareBitmap verwendet COM-Interoperabilität, um den Rohpuffer mit den Pixeldaten verfügbar zu machen.

Um COM-Interop zu verwenden, müssen Sie einen Verweis auf den System.Runtime.InteropServices-Namespace in Ihr Projekt einfügen.

using System.Runtime.InteropServices;

Initialisieren Sie die IMemoryBufferByteAccess-COM-Schnittstelle, indem Sie den folgenden Code in Ihrem Namespace hinzufügen.

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

Erstellen Sie eine neue SoftwareBitmap mit Pixelformat und Größe, die Sie benötigen. Oder verwenden Sie eine vorhandene SoftwareBitmap, für die Sie die Pixeldaten bearbeiten möchten. Rufen Sie SoftwareBitmap.LockBuffer auf, um eine Instanz der BitmapBuffer abzurufen, die den Pixeldatenpuffer darstellt. Wandeln Sie den BitmapBuffer in die IMemoryBufferByteAccess-COM-Schnittstelle um, und rufen Sie dann IMemoryBufferByteAccess.GetBuffer auf, um ein Bytearray mit Daten aufzufüllen. Verwenden Sie die BitmapBuffer.GetPlaneDescription-Methode, um ein BitmapPlaneDescription-Objekt abzurufen, das Sie beim Berechnen des Offsets in den Puffer für jedes Pixel unterstützt.

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

Da diese Methode auf den rohen Puffer zugreift, der den Windows-Runtime Typen zugrunde liegt, muss sie mithilfe des unsicheren Schlüsselworts (Keyword) deklariert werden. Sie müssen Ihr Projekt auch in Microsoft Visual Studio so konfigurieren, dass die Kompilierung unsicherer Code zugelassen wird, indem Sie die Seite Eigenschaften des Projekts öffnen, auf die Seite Build-Eigenschaften klicken und das Kontrollkästchen Unsicheren Code zulassen aktivieren.

Erstellen einer SoftwareBitmap auf einer Direct3D-Oberfläche

Um ein SoftwareBitmap-Objekt aus einer Direct3D-Oberfläche zu erstellen, müssen Sie den Windows.Graphics.DirectX.Direct3D11-Namespace in Ihr Projekt einschließen.

using Windows.Graphics.DirectX.Direct3D11;

Rufen Sie CreateCopyFromSurfaceAsync auf, um eine neue SoftwareBitmap aus der Oberfläche zu erstellen. Wie der Name angibt, verfügt die neue SoftwareBitmap über eine separate Kopie der Bilddaten. Änderungen an der SoftwareBitmap wirken sich nicht auf die Direct3D-Oberfläche aus.

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

Konvertieren einer SoftwareBitmap in ein anderes Pixelformat

Die SoftwareBitmap-Klasse stellt die statische Methode Convert bereit, mit der Sie problemlos eine neue SoftwareBitmap erstellen können, die das Pixelformat und den Alphamodus verwendet, den Sie aus einer vorhandenen SoftwareBitmap angeben. Beachten Sie, dass die neu erstellte Bitmap über eine separate Kopie der Bilddaten verfügt. Änderungen an der neuen Bitmap wirken sich nicht auf die Quellbitmap aus.

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

Transcodieren einer Bilddatei

Sie können eine Bilddatei direkt von einem BitmapDecoder in einen BitmapEncoder transcodieren. Erstellen Sie einen IRandomAccessStream aus der Datei, die transcodiert werden soll. Erstellen Sie einen neuen BitmapDecoder aus dem Eingabedatenstrom. Erstellen Sie einen neuen InMemoryRandomAccessStream für den Encoder zum Schreiben und Aufrufen von BitmapEncoder.CreateForTranscodingAsync, wobei der In-Memory-Datenstrom und das Decoderobjekt übergeben werden. Codieren von Optionen wird bei der Transcodierung nicht unterstützt; Stattdessen sollten Sie CreateAsync verwenden. Alle Eigenschaften in der Eingabebilddatei, die Sie nicht speziell für den Encoder festlegen, werden unverändert in die Ausgabedatei geschrieben. Rufen Sie FlushAsync auf, damit der Encoder im Arbeitsspeicherdatenstrom codiert wird. Suchen Sie schließlich den Dateidatenstrom und den Arbeitsspeicherdatenstrom an den Anfang, und rufen Sie CopyAsync auf, um den Arbeitsspeicherdatenstrom in den Dateidatenstrom zu schreiben.

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