Создание, редактирование и сохранение точечных рисунков

В этой статье объясняется, как загружать и сохранять файлы изображений с помощью BitmapDecoder и BitmapEncoder и как использовать объект SoftwareBitmap для представления растровых изображений.

Класс SoftwareBitmap — это универсальный API, который можно создать из нескольких источников, включая файлы изображений, объекты WriteableBitmap, поверхности Direct3D и код. SoftwareBitmap позволяет легко преобразовывать между различными форматами пикселей и альфа-режимами, а также обеспечивает низкоуровневый доступ к данным пикселей. Кроме того, SoftwareBitmap — это распространенный интерфейс, используемый несколькими функциями Windows, в том числе:

  • CapturedFrame позволяет получать кадры, захваченные камерой как SoftwareBitmap.

  • VideoFrame позволяет получить представление SoftwareBitmap видеофрейма.

  • FaceDetector позволяет обнаруживать лица в SoftwareBitmap.

В примере кода в этой статье используются API из следующих пространств имен.

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

Создание SoftwareBitmap из файла изображения с помощью BitmapDecoder

Чтобы создать SoftwareBitmap из файла, получите экземпляр служба хранилища File, содержащий данные изображения. В этом примере используется FileOpenPicker , чтобы разрешить пользователю выбрать файл изображения.

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

Вызовите метод OpenAsync объекта служба хранилища File, чтобы получить поток случайного доступа, содержащий данные изображения. Вызовите статический метод BitmapDecoder.CreateAsync, чтобы получить экземпляр класса BitmapDecoder для указанного потока. Вызовите GetSoftwareBitmapAsync, чтобы получить объект SoftwareBitmap, содержащий изображение.

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

Сохранение SoftwareBitmap в файл с помощью BitmapEncoder

Чтобы сохранить SoftwareBitmap в файл, получите экземпляр служба хранилища File, в котором будет сохранен образ. В этом примере используется FileSavePicker , чтобы разрешить пользователю выбрать выходной файл.

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

Вызовите метод OpenAsync объекта служба хранилища File, чтобы получить поток случайного доступа, в который будет записан образ. Вызовите статический метод BitmapEncoder.CreateAsync, чтобы получить экземпляр класса BitmapEncoder для указанного потока. Первый параметр CreateAsync — это GUID, представляющий кодек, который должен использоваться для кодирования изображения. Класс BitmapEncoder предоставляет свойство, содержащее идентификатор для каждого кодека, поддерживаемого кодировщиком, например JpegEncoderId.

Используйте метод SetSoftwareBitmap, чтобы задать изображение, которое будет закодировано. Значения свойства BitmapTransform можно задать для применения базовых преобразований к изображению во время кодирования. Свойство IsThumbnailGenerated определяет, создается ли эскиз кодировщиком. Обратите внимание, что не все форматы файлов поддерживают эскизы, поэтому при использовании этой функции следует поймать ошибку неподдерживаемой операции, которая будет возникать, если эскизы не поддерживаются.

Вызов FlushAsync, чтобы кодировщик записывал данные изображения в указанный файл.

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


    }
}

При создании BitmapEncoder можно указать дополнительные параметры кодирования, создав объект BitmapPropertySet и заполнив его одним или несколькими объектами BitmapTypedValue, представляющими параметры кодировщика. Список поддерживаемых параметров кодировщика см . в справочнике по параметрам 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
);

Использование SoftwareBitmap с элементом управления изображением XAML

Чтобы отобразить изображение на странице XAML с помощью элемента управления "Изображение", сначала определите элемент управления "Изображение" на странице XAML.

<Image x:Name="imageControl"/>

В настоящее время элемент управления "Образ" поддерживает только изображения, использующие кодировку BGRA8 и предварительно умноженные или нет альфа-канала. Прежде чем пытаться отобразить изображение, протестируйте, чтобы убедиться, что он имеет правильный формат, а если нет, используйте статический метод Convert SoftwareBitmap для преобразования изображения в поддерживаемый формат.

Создайте объект SoftwareBitmapSource . Задайте содержимое исходного объекта путем вызова SetBitmapAsync, передаваемого в SoftwareBitmap. Затем можно задать свойство Source элемента управления Image для только что созданного SoftwareBitmapSource.

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;

Вы также можете использовать SoftwareBitmapSource для задания SoftwareBitmap в качестве ImageSource для ImageBrush.

Создание SoftwareBitmap из объекта WriteableBitmap

Вы можете создать SoftwareBitmap из существующего объекта WriteableBitmap, вызвав SoftwareBitmap.CreateCopyFromBuffer и указав свойство PixelBuffer объекта WriteableBitmap, чтобы задать данные пикселей. Второй аргумент позволяет запрашивать формат пикселей для только что созданного объекта WriteableBitmap. Свойства PixelWidth и PixelHeight объекта WriteableBitmap можно использовать для указания измерений нового изображения.

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

Создание или изменение SoftwareBitmap программным способом

До сих пор в этой статье рассматривается работа с файлами изображений. Вы также можете создать новую программу SoftwareBitmap в коде и использовать тот же метод для доступа к данным пикселей SoftwareBitmap и их изменения.

SoftwareBitmap использует COM-взаимодействие для предоставления необработанного буфера, содержащего данные пикселей.

Чтобы использовать COM-взаимодействие, необходимо включить ссылку на пространство имен System.Runtime.InteropServices в проекте.

using System.Runtime.InteropServices;

Инициализировать com-интерфейс IMemoryBufferByteAccess , добавив следующий код в пространство имен.

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

Создайте файл SoftwareBitmap с нужным форматом пикселей и размером. Или используйте существующий SoftwareBitmap , для которого требуется изменить данные пикселей. Вызовите SoftwareBitmap.LockBuffer для получения экземпляра класса BitmapBuffer, представляющего буфер данных пикселей. Приведите bitmapBuffer к com-интерфейсу IMemoryBufferByteAccess, а затем вызовите IMemoryBufferByteAccess.GetBuffer, чтобы заполнить массив байтов данными. Используйте метод BitmapBuffer.GetPlaneDescription, чтобы получить объект BitmapPlaneDescription, который поможет вычислить смещение в буфер для каждого пикселя.

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

Так как этот метод обращается к необработанному буферу, лежащему в основе типов среда выполнения Windows, он должен быть объявлен с помощью небезопасного ключевое слово. Также необходимо настроить проект в Microsoft Visual Studio, чтобы разрешить компиляцию небезопасного кода, открыв страницу свойств проекта, щелкнув страницу свойств сборки и выбрав поле "Разрешить небезопасный код" проверка.

Создание SoftwareBitmap из поверхности Direct3D

Чтобы создать объект SoftwareBitmap из поверхности Direct3D, необходимо включить пространство имен Windows.Graphics.DirectX.Direct3D11 в проект.

using Windows.Graphics.DirectX.Direct3D11;

Вызовите CreateCopyFromSurfaceAsync , чтобы создать приложение SoftwareBitmap из поверхности. Как указано имя, новый SoftwareBitmap имеет отдельную копию данных изображения. Изменения в SoftwareBitmap не будут влиять на поверхность Direct3D.

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

Преобразование SoftwareBitmap в другой формат пикселей

Класс SoftwareBitmap предоставляет статический метод Convert, который позволяет легко создать новый SoftwareBitmap, использующий формат пикселей и альфа-режим, указанный из существующего SoftwareBitmap. Обратите внимание, что только что созданная растровая карта имеет отдельную копию данных изображения. Изменения новой растровой карты не влияют на исходное растровое изображение.

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

Перекодирование файла изображения

Файл изображения можно перекодировать непосредственно из BitmapDecoder в BitmapEncoder. Создайте IRandomAccessStream из файла, который будет перекодирован. Создайте новый bitmapDecoder из входного потока. Создайте новый InMemoryRandomAccessStream для кодировщика для записи и вызова BitmapEncoder.CreateForTranscodingAsync, передавая поток в памяти и объект декодировщика. Параметры кодирования не поддерживаются при перекодировании; вместо этого следует использовать CreateAsync. Все свойства входного файла изображения, которые не заданы в кодировщике, будут записаны в выходной файл без изменений. Вызов FlushAsync, чтобы кодировщик закодировали в поток в памяти. Наконец, ищите файловый поток и поток в памяти в начале и вызовите CopyAsync , чтобы записать поток в памяти в файловый поток.

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