如何在C# WinRT/winmd中调整图像大小?

15

我有一个简单的问题,但迄今为止我没有找到答案:在C# WinRT/WinMD项目中如何调整jpeg图像大小并将其保存为新的jpeg格式?

我正在开发Windows 8 Metro应用程序,用于从某个站点下载每日图像并将其显示在Live Tile上。问题是图像必须小于1024x1024且小于200kB,否则它将不会显示在Tile上:http://msdn.microsoft.com/en-us/library/windows/apps/hh465403.aspx

如果我有一张更大的图像,如何调整大小以适合Live Tile呢?我只考虑简单的调整大小,例如保持纵横比的情况下将宽度/2和高度/2。

这里的特定要求是代码必须作为Windows Runtime组件运行,因此WriteableBitmapEx库在这里无法使用 - 它仅适用于常规WinRT项目。即使有一个WriteableBitmapEx作为winmd项目的分支,但它还远未准备好。

6个回答

18

以下是如何进行缩放和裁剪的示例,取自此处

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

}

你认为当代码在鼠标移动时,它会是一个非常快速的性能解决方案吗?你觉得呢?它的性能比WriteableBitmapEX更好吗? - Apoorv
@Apoorv 我不知道。 - N_A

12

使用更简单的代码重新调整图像大小,而不是裁剪。下面的代码将图像调整为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();
    }
}

来源


1
使用这个方法,生成的瓦片图像质量非常差... 有什么建议吗? - bc3tech
如果您从此代码中获得了较差的图像质量,请尝试使用“transform.InterpolationMode = BitmapInterpolationMode.Fant”。 - evilkos

7
以下是我在大量搜索和试错编码后提出的解决方案:
这里的目标是找出如何在WinRT中操作图像,特别是在后台任务中。 后台任务甚至比普通的WinRT项目更受限制,因为它们必须是Windows Runtime Component类型。 NuGet上99%针对WinRT的可用库仅针对默认的WinRT项目,因此它们不能用于Windows Runtime Component项目。
起初我尝试使用众所周知的WriteableBitmapEx库 - 将必要的代码移植到我的winmd项目中。甚至有WBE项目的分支针对winmd, 但它还没有完成。 我添加了[ReadOnlyArray],[WriteOnlyArray]属性到类型数组的方法参数,并将项目命名空间更改为不以“Windows”开头的名称,才使其编译成功 - 这是winmd项目的限制。
即使我能在后台任务项目中使用此库,但它无法工作,因为如我发现的那样,WriteableBitmap必须在UI线程中实例化,而据我所知,在后台任务中这是不可能的。

与此同时,我还发现了这篇有关WinRT中图像处理的MSDN文章。其中大部分示例都只在JavaScript部分,所以我首先需要将其转换为C#。我还发现了这篇有用的StackOverflow文章,它是关于WinRT中图像处理的。

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

在这个示例中,我们打开一个文件,将其解码为字节数组,然后使用不同的大小/格式/质量对其进行编码到新文件中。
结果是完全可用的图像处理,即使在 Windows Runtime 组件类中也是如此,而且无需使用 WriteableBitmapEx 库。

这个有多快?我需要一个可以在鼠标移动事件上实现的解决方案,所以它必须在我的指针所在的位置裁剪... 它适合处理2K图像吗?我正在使用WriteableBitmapEx..它很慢。 - Apoorv
你有这个的示例代码吗?它要求TargetJpegQuality类。 - Apoorv

2
这里有一个更简短的版本,没有访问像素数据的开销。
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();
}

0
我刚刚花了一个半小时来解决这个问题,我有一个字节数组,其中包含一个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;            
    }

0
如果您想要高质量的图像,那么请在 BitmapTransform 中添加 InterpolationMode = BitmapInterpolationMode.Fant,以下是示例:
``` public static async Task 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();
            }
        }`

这段代码可以运行,但是它没有减小那张图片的尺寸。 所以我发现我们可以创建一个缩略图来代替调整图片大小。 https://code.msdn.microsoft.com/windowsapps/File-thumbnails-sample-17575959/view/SourceCode#content - Kishan.JSK

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接