Как изменить размер изображения в С# WinRT/winmd?
У меня есть простой вопрос, но пока я не нашел ответа: как изменить размер jpeg-изображения в проекте С# WinRT/WinMD и сохранить его как новый jpeg?
Я разрабатываю приложение Windows 8 Metro для загрузки ежедневного изображения с определенного сайта и отображения его на Live Tile. Проблема заключается в том, что изображение должно быть меньше 1024x1024 и меньше 200kB, иначе оно не будет отображаться на плитке:
http://msdn.microsoft.com/en-us/library/windows/apps/hh465403.aspx
Если я получил увеличенное изображение, как изменить его размер, чтобы он был подходящим для Live Tile? Я думаю о простом изменении размера, например width/2 и height/2, сохраняя пропорции аспект.
Конкретным требованием здесь является то, что код должен выполняться как компонент Windows Runtime, поэтому библиотека WriteableBitmapEx не будет работать здесь - она доступна только для обычных проектов WinRT. Существует даже ветка для WriteableBitmapEx как проект winmd, но она далека от готовности.
Ответы
Ответ 1
Пример того, как масштабировать и обрезать, взятые из здесь:
async private void BitmapTransformTest()
{
// hard coded image location
string filePath = "C:\\Users\\Public\\Pictures\\Sample Pictures\\fantasy-dragons-wallpaper.jpg";
StorageFile file = await StorageFile.GetFileFromPathAsync(filePath);
if (file == null)
return;
// create a stream from the file and decode the image
var fileStream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read);
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream);
// create a new stream and encoder for the new image
InMemoryRandomAccessStream ras = new InMemoryRandomAccessStream();
BitmapEncoder enc = await BitmapEncoder.CreateForTranscodingAsync(ras, decoder);
// convert the entire bitmap to a 100px by 100px bitmap
enc.BitmapTransform.ScaledHeight = 100;
enc.BitmapTransform.ScaledWidth = 100;
BitmapBounds bounds = new BitmapBounds();
bounds.Height = 50;
bounds.Width = 50;
bounds.X = 50;
bounds.Y = 50;
enc.BitmapTransform.Bounds = bounds;
// write out to the stream
try
{
await enc.FlushAsync();
}
catch (Exception ex)
{
string s = ex.ToString();
}
// render the stream to the screen
BitmapImage bImg = new BitmapImage();
bImg.SetSource(ras);
img.Source = bImg; // image element in xaml
}
Ответ 2
Более простой код для изменения размера изображения, а не обрезки. Следующий код изменяет размер изображения как 80x80
using (var sourceStream = await sourceFile.OpenAsync(FileAccessMode.Read))
{
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(sourceStream);
BitmapTransform transform = new BitmapTransform() { ScaledHeight = 80, ScaledWidth = 80 };
PixelDataProvider pixelData = await decoder.GetPixelDataAsync(
BitmapPixelFormat.Rgba8,
BitmapAlphaMode.Straight,
transform,
ExifOrientationMode.RespectExifOrientation,
ColorManagementMode.DoNotColorManage);
using (var destinationStream = await destinationFile.OpenAsync(FileAccessMode.ReadWrite))
{
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, destinationStream);
encoder.SetPixelData(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied, 80, 80, 96, 96, pixelData.DetachPixelData());
await encoder.FlushAsync();
}
}
Источник
Ответ 3
Итак, вот мое решение, с которым я пришел после многого поискового запроса и кодирования проб/ошибок:
Целью здесь было выяснить, как манипулировать изображениями в WinRT, в частности, в Background Tasks. Фоновые задачи еще более ограничены, чем обычные проекты WinRT, потому что они должны быть типа компонент Windows Runtime. 99% доступных библиотек в NuGet для WinRT нацелены только на проекты WinRT по умолчанию, поэтому они не могут использоваться в проектах Windows Runtime Component.
Сначала я попытался использовать известную библиотеку WriteableBitmapEx - портирование нужного кода в мой проект winmd. Существует даже ветвь проекта WBE, ориентированного на winmd, но не завершена. Я скомпилировал его после добавления атрибутов [ReadOnlyArray], [WriteOnlyArray] к параметрам метода массива типов, а также после смены пространства имен проектов на то, что не начинается с "Windows" - ограничение проекта winmd.
Несмотря на то, что я мог использовать эту библиотеку в моем проекте Background Task, она не работала, потому что, как я обнаружил, WriteableBitmap должен быть создан в потоке пользовательского интерфейса, и это невозможно, насколько мне известно в фоновой задаче.
Тем временем я также нашел эту статью статью MSDN об обработке изображений в WinRT. Большинство образцов есть только в разделе JavaScript, поэтому мне пришлось сначала преобразовать его в С#. Я также нашел эту полезную fooobar.com/questions/404754/....
internal static async Task LoadTileImageInternalAsync(string imagePath)
{
string tileName = imagePath.GetHashedTileName();
StorageFile origFile = await ApplicationData.Current.LocalFolder.GetFileAsync(imagePath);
// open file for the new tile image file
StorageFile tileFile = await ApplicationData.Current.LocalFolder.CreateFileAsync(tileName, CreationCollisionOption.ReplaceExisting);
using (IRandomAccessStream tileStream = await tileFile.OpenAsync(FileAccessMode.ReadWrite))
{
// get width and height from the original image
IRandomAccessStreamWithContentType stream = await origFile.OpenReadAsync();
ImageProperties properties = await origFile.Properties.GetImagePropertiesAsync();
uint width = properties.Width;
uint height = properties.Height;
// get proper decoder for the input file - jpg/png/gif
BitmapDecoder decoder = await GetProperDecoder(stream, imagePath);
if (decoder == null) return; // should not happen
// get byte array of actual decoded image
PixelDataProvider data = await decoder.GetPixelDataAsync();
byte[] bytes = data.DetachPixelData();
// create encoder for saving the tile image
BitmapPropertySet propertySet = new BitmapPropertySet();
// create class representing target jpeg quality - a bit obscure, but it works
BitmapTypedValue qualityValue = new BitmapTypedValue(TargetJpegQuality, PropertyType.Single);
propertySet.Add("ImageQuality", qualityValue);
// create the target jpeg decoder
BitmapEncoder be = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, tileStream, propertySet);
be.SetPixelData(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Straight, width, height, 96.0, 96.0, bytes);
// crop the image, if it too big
if (width > MaxImageWidth || height > MaxImageHeight)
{
BitmapBounds bounds = new BitmapBounds();
if (width > MaxImageWidth)
{
bounds.Width = MaxImageWidth;
bounds.X = (width - MaxImageWidth) / 2;
}
else bounds.Width = width;
if (height > MaxImageHeight)
{
bounds.Height = MaxImageHeight;
bounds.Y = (height - MaxImageHeight) / 2;
}
else bounds.Height = height;
be.BitmapTransform.Bounds = bounds;
}
// save the target jpg to the file
await be.FlushAsync();
}
}
private static async Task<BitmapDecoder> GetProperDecoder(IRandomAccessStreamWithContentType stream, string imagePath)
{
string ext = Path.GetExtension(imagePath);
switch (ext)
{
case ".jpg":
case ".jpeg":
return await BitmapDecoder.CreateAsync(BitmapDecoder.JpegDecoderId, stream);
case ".png":
return await BitmapDecoder.CreateAsync(BitmapDecoder.PngDecoderId, stream);
case ".gif":
return await BitmapDecoder.CreateAsync(BitmapDecoder.GifDecoderId, stream);
}
return null;
}
В этом примере мы открываем один файл, декодируем его в массив байтов и кодируем его обратно в новый файл с другим размером/форматом/качеством.
Результат - полностью работающая обработка изображений даже в классе компонентов Runtime Windows и без библиотеки WriteableBitmapEx.
Ответ 4
Вот еще более короткая версия, без накладных расходов на доступ к данным о пикселях.
using (var sourceFileStream = await sourceFile.OpenAsync(Windows.Storage.FileAccessMode.Read))
using (var destFileStream = await destinationFile.OpenAsync(FileAccessMode.ReadWrite))
{
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(sourceFileStream);
BitmapEncoder enc = await BitmapEncoder.CreateForTranscodingAsync(destFileStream, decoder);
enc.BitmapTransform.ScaledWidth = newWidth;
enc.BitmapTransform.ScaledHeight = newHeight;
await enc.FlushAsync();
await destFileStream.FlushAsync();
}
Ответ 5
Я только что провел последний час и половину, пытаясь понять это, у меня есть массив байтов, который является JPG, и попытался ответить на этот вопрос... Я не мог заставить его работать, поэтому я поднимаю новый ответ... Надеюсь, это поможет кому-то еще... Я конвертирую JPG в 250/250 пикселей
private async Task<BitmapImage> ByteArrayToBitmapImage(byte[] byteArray)
{
BitmapImage image = new BitmapImage();
using (InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream())
{
using (DataWriter writer = new DataWriter(stream.GetOutputStreamAt(0)))
{
writer.WriteBytes((byte[])byteArray);
writer.StoreAsync().GetResults();
}
image.SetSource(stream);
}
image.DecodePixelHeight = 250;
image.DecodePixelWidth = 250;
return image;
}
Ответ 6
если вы хотите качественное изображение, затем добавьте
<Б > InterpolationMode = BitmapInterpolationMode.Fant в BitmapTransform,
вот пример
`
public static async Задача ResizeImage (Windows.Storage.StorageFile imgeTOBytes, int maxWidth, int maxHeight) {
using (var sourceStream = await imgeTOBytes.OpenAsync(FileAccessMode.Read))
{
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(sourceStream);
double widthRatio = (double)maxWidth / decoder.OrientedPixelWidth;
double heightRatio = (double)maxHeight / decoder.OrientedPixelHeight;
double scaleRatio = Math.Min(widthRatio, heightRatio);
uint aspectHeight = (uint)Math.Floor((double)decoder.OrientedPixelHeight * scaleRatio);
uint aspectWidth = (uint)Math.Floor((double)decoder.OrientedPixelWidth * scaleRatio);
BitmapTransform transform = new BitmapTransform() { InterpolationMode = BitmapInterpolationMode.Fant, ScaledHeight = aspectHeight, ScaledWidth = aspectWidth };
PixelDataProvider pixelData = await decoder.GetPixelDataAsync(
BitmapPixelFormat.Rgba8,
BitmapAlphaMode.Premultiplied,
transform,
ExifOrientationMode.RespectExifOrientation,
ColorManagementMode.DoNotColorManage);
using (var destinationStream = await imgeTOBytes.OpenAsync(FileAccessMode.ReadWrite))
{
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, destinationStream);
encoder.SetPixelData(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Straight, aspectWidth, aspectHeight, 96, 96, pixelData.DetachPixelData());
await encoder.FlushAsync();
}
}`