Bitmapafbeeldingen maken, bewerken en opslaan

In dit artikel wordt uitgelegd hoe u afbeeldingsbestanden laadt en opslaat met BitmapDecoder en BitmapEncoder en hoe u het object SoftwareBitmap gebruikt om bitmapafbeeldingen weer te geven.

De SoftwareBitmap-klasse is een veelzijdige API die kan worden gemaakt op basis van meerdere bronnen, waaronder afbeeldingsbestanden, WriteableBitmap-objecten , Direct3D-oppervlakken en code. Met SoftwareBitmap kunt u eenvoudig converteren tussen verschillende pixelindelingen en alfamodi en kunt u toegang op laag niveau tot pixelgegevens toestaan. Bovendien is SoftwareBitmap een algemene interface die wordt gebruikt door meerdere functies van Windows, waaronder:

  • CapturedFrame stelt u in staat frames die door de camera zijn vastgelegd op te halen als een SoftwareBitmap.

  • VideoFrame kunt u een SoftwareBitmap weergave krijgen van een VideoFrame.

  • FaceDetector kunt u gezichten in een SoftwareBitmap detecteren.

De voorbeeldcode in dit artikel maakt gebruik van API's uit de volgende naamruimten.

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;

Een SoftwareBitmap maken op basis van een afbeeldingsbestand met BitmapDecoder

Als u een SoftwareBitmap wilt maken op basis van een bestand, haalt u een exemplaar op van StorageFile met de afbeeldingsgegevens. In dit voorbeeld wordt een FileOpenPicker gebruikt om de gebruiker toe te staan een afbeeldingsbestand te selecteren.

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

Roep de OpenAsync-methode van het StorageFile-object aan om een willekeurige toegangsstroom op te halen die de afbeeldingsgegevens bevat. Roep de statische methode BitmapDecoder.CreateAsync aan om een exemplaar op te halen van de klasse BitmapDecoder klasse voor de opgegeven stream. Roep GetSoftwareBitmapAsync aan om een SoftwareBitmap object met de afbeelding op te halen.

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

Een SoftwareBitmap opslaan in een bestand met BitmapEncoder

Om een SoftwareBitmap op te slaan in een bestand, haalt u een StorageFile-exemplaar op waarin de afbeelding wordt opgeslagen. In dit voorbeeld wordt een FileSavePicker gebruikt, zodat de gebruiker een uitvoerbestand kan selecteren.

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

Roep de OpenAsync-methode van het StorageFile-object aan om een willekeurige toegangsstroom op te halen waarnaar de afbeelding wordt geschreven. Roep de statische methode BitmapEncoder.CreateAsync aan om een exemplaar op te halen van de BitmapEncoder klasse voor de opgegeven stream. De eerste parameter voor CreateAsync is een GUID die de codec vertegenwoordigt die moet worden gebruikt om de afbeelding te coderen. BitmapEncoder-klasse toont een eigenschap met de id voor elke codec die wordt ondersteund door de encoder, zoals JpegEncoderId.

Gebruik de methode SetSoftwareBitmap om de afbeelding in te stellen die wordt gecodeerd. U kunt waarden van de eigenschap BitmapTransform instellen om eenvoudige transformaties toe te passen op de afbeelding terwijl deze wordt gecodeerd. De eigenschap IsThumbnailGenerated bepaalt of een miniatuur wordt gegenereerd door de encoder. Houd er rekening mee dat niet alle bestandsindelingen miniaturen ondersteunen, dus als u deze functie gebruikt, moet u de niet-ondersteunde bewerkingsfout ondervangen die wordt gegenereerd als miniaturen niet worden ondersteund.

Roep FlushAsync aan om ervoor te zorgen dat de encoder de afbeeldingsgegevens naar het opgegeven bestand schrijft.

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

U kunt extra coderingsopties opgeven wanneer u de BitmapEncoder maakt door een nieuwe BitmapPropertySet object en het vullen met een of meer BitmapTypedValue objecten die de coderingsinstellingen vertegenwoordigen. Zie De naslaginformatie over BitmapEncoder-opties voor een lijst met ondersteunde coderingsopties.

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

SoftwareBitmap gebruiken met een XAML-afbeeldingsbesturingselement

Als u een afbeelding wilt weergeven op een XAML-pagina met behulp van het besturingselement Afbeelding , definieert u eerst een besturingselement Afbeelding op uw XAML-pagina.

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

Momenteel ondersteunt het besturingselement Afbeelding alleen afbeeldingen die gebruikmaken van BGRA8-codering en vooraf vermenigvuldigd of geen alfakanaal. Voordat u een afbeelding probeert weer te geven, test u of deze de juiste indeling heeft. Als dat niet het geval is, gebruikt u de statischeconversiemethode SoftwareBitmap om de afbeelding te converteren naar de ondersteunde indeling.

Maak een nieuw SoftwareBitmapSource-object . Stel de inhoud van het bronobject in door SetBitmapAsync aan te roepen, waarbij een SoftwareBitmap wordt doorgegeven. Vervolgens kunt u de eigenschap Source van het besturingselement Image instellen op de zojuist gemaakte 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;
}

U kunt SoftwareBitmapSource ook gebruiken om een SoftwareBitmap in te stellen als imagesource voor een ImageBrush.

Een SoftwareBitmap maken op basis van een WriteableBitmap

U kunt een SoftwareBitmap maken op basis van een bestaande WriteableBitmap door SoftwareBitmap.CreateCopyFromBuffer aan te roepen en de eigenschap PixelBuffer van de WriteableBitmap op te geven om de pixelgegevens in te stellen. Met het tweede argument kunt u de pixelindeling voor de zojuist gemaakte SoftwareBitmap opgeven. U kunt de eigenschappen PixelWidth en PixelHeight van de WriteableBitmap gebruiken om de afmetingen van de nieuwe afbeelding op te geven.

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

Programmatisch een SoftwareBitmap maken of bewerken

Tot nu toe heeft dit onderwerp betrekking op het werken met afbeeldingsbestanden. U kunt ook programmatisch een nieuwe SoftwareBitmap in code maken en dezelfde techniek gebruiken om de pixelgegevens van de SoftwareBitmap te openen en te wijzigen.

Gebruik de methode CopyFromBuffer om een SoftwareBitmap van een bytematrix te vullen en CopyToBuffer om pixelgegevens naar een bytematrix te kopiëren voor lezen of wijzigen. Als u de extensiemethode AsBuffer wilt gebruiken om een bytematrix te verpakken als een IBuffer, neem de System.Runtime.InteropServices.WindowsRuntime naamruimte op (dit is opgenomen in de SnippetNamespaces met behulp van instructies bovenaan dit artikel).

Maak een nieuwe SoftwareBitmap met de gewenste pixelindeling en -grootte. Wijs een bytematrix groot genoeg toe om de pixelgegevens op te slaan, vul deze met de gewenste waarden in en roep CopyFromBuffer aan om de gegevens naar de bitmap te schrijven.

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

Een SoftwareBitmap maken op basis van een Direct3D-oppervlak

Als u een SoftwareBitmap-object wilt maken van een Direct3D-oppervlak, moet u de naamruimte Windows.Graphics.DirectX.Direct3D11 in uw project opnemen.

using Windows.Graphics.DirectX.Direct3D11;

Roep CreateCopyFromSurfaceAsync aan om een nieuwe SoftwareBitmap te maken op basis van het oppervlak. Zoals de naam aangeeft, heeft de nieuwe SoftwareBitmap een afzonderlijke kopie van de afbeeldingsgegevens. Wijzigingen in de SoftwareBitmap hebben geen effect op het Direct3D-oppervlak.

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

    return softwareBitmap;
}

Een SoftwareBitmap converteren naar een andere pixelindeling

De SoftwareBitmap-klasse biedt de statische methode Convert waarmee u eenvoudig een nieuwe SoftwareBitmap kunt maken die gebruikmaakt van de pixelindeling en de alfamodus die u opgeeft op basis van een bestaande SoftwareBitmap. Houd er rekening mee dat de zojuist gemaakte bitmap een afzonderlijke kopie van de afbeeldingsgegevens heeft. Wijzigingen in de nieuwe bitmap hebben geen invloed op de bron bitmap.

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

Een afbeeldingsbestand transcoderen

U kunt een afbeeldingsbestand rechtstreeks vanuit een BitmapDecoder transcoderen naar een BitmapEncoder. Maak een IRandomAccessStream van het bestand dat moet worden getranscodeerd. Maak een nieuwe BitmapDecoder op basis van de invoerstroom. Maak een nieuwe InMemoryRandomAccessStream zodat de encoder ernaar kan schrijven, en roep vervolgens BitmapEncoder.CreateForTranscodingAsync aan, waarbij u de in-memorystream en het decoderobject doorgeeft. Coderingsopties worden niet ondersteund bij transcodering; In plaats daarvan moet u CreateAsync gebruiken. Alle eigenschappen in het invoerafbeeldingsbestand die u niet specifiek voor de encoder instelt, worden ongewijzigd naar het uitvoerbestand geschreven. Roep FlushAsync aan om de encoder naar de geheugenstroom te laten coderen. Zet ten slotte de bestandsstroom en de geheugenstroom terug naar het begin en roep CopyAsync aan om de geheugenstroom naar de bestandsstroom te kopiëren.

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