You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET MAUI中如何旋转ImageSource并保存到设备?

.NET MAUI 旋转ImageSource并保存到设备的实现方案

一、准备工作

先安装SkiaSharp相关NuGet包:
SkiaSharp.Views.Maui.Controls
SkiaSharp.NativeAssets.Android(Android平台)
SkiaSharp.NativeAssets.iOS(iOS平台)

二、旋转ImageSource的实现

核心思路

ImageSource是抽象类,无法直接操作像素。需先将其转换为SKBitmap(SkiaSharp位图对象),完成旋转后再转回ImageSource。

代码示例

using SkiaSharp;
using System.IO;
using Microsoft.Maui.Graphics;

public static class ImageHelper
{
    // 旋转ImageSource,rotationAngle为旋转角度(如90、180、270)
    public static async Task<ImageSource> RotateImageSourceAsync(ImageSource source, float rotationAngle)
    {
        StreamImageSource streamSource;
        if (source is not StreamImageSource tempSource)
        {
            // 非Stream类型的ImageSource先转成StreamImageSource
            streamSource = await ConvertToStreamImageSourceAsync(source);
        }
        else
        {
            streamSource = tempSource;
        }

        using var stream = await streamSource.Stream(CancellationToken.None);
        using var originalBitmap = SKBitmap.Decode(stream);
        
        // 计算旋转后的画布尺寸
        var rotatedWidth = rotationAngle % 180 == 0 ? originalBitmap.Width : originalBitmap.Height;
        var rotatedHeight = rotationAngle % 180 == 0 ? originalBitmap.Height : originalBitmap.Width;
        
        // 创建旋转后的位图并绘制
        using var rotatedBitmap = new SKBitmap(rotatedWidth, rotatedHeight);
        using var canvas = new SKCanvas(rotatedBitmap);
        
        canvas.Translate(rotatedWidth / 2f, rotatedHeight / 2f);
        canvas.RotateDegrees(rotationAngle);
        canvas.Translate(-originalBitmap.Width / 2f, -originalBitmap.Height / 2f);
        canvas.DrawBitmap(originalBitmap, 0, 0);
        
        // 将旋转后的位图转成Stream,再封装为ImageSource
        using var ms = new MemoryStream();
        rotatedBitmap.Encode(SKEncodedImageFormat.Png, 100).SaveTo(ms);
        ms.Position = 0;
        
        return ImageSource.FromStream(() => ms);
    }

    // 辅助方法:将任意ImageSource转成StreamImageSource
    private static async Task<StreamImageSource> ConvertToStreamImageSourceAsync(ImageSource source)
    {
        if (source is FileImageSource fileSource)
        {
            var stream = File.OpenRead(fileSource.File);
            return ImageSource.FromStream(() => stream) as StreamImageSource;
        }
        else if (source is UriImageSource uriSource)
        {
            using var client = new HttpClient();
            var stream = await client.GetStreamAsync(uriSource.Uri);
            var memoryStream = new MemoryStream();
            await stream.CopyToAsync(memoryStream);
            memoryStream.Position = 0;
            return ImageSource.FromStream(() => memoryStream) as StreamImageSource;
        }
        throw new NotSupportedException("不支持的ImageSource类型");
    }
}

三、将处理后的ImageSource保存到设备

核心思路

把旋转后的ImageSource转成Stream,再通过MAUI的FileSystem类写入设备存储(应用私有目录或公共图库)。

代码示例

using System.IO;
using Microsoft.Maui.Storage;

public static class ImageSaver
{
    // 保存到应用私有目录
    public static async Task<string> SaveToAppDirectoryAsync(ImageSource source, string fileName)
    {
        var streamSource = await ImageHelper.ConvertToStreamImageSourceAsync(source);
        using var stream = await streamSource.Stream(CancellationToken.None);
        
        var directoryPath = FileSystem.AppDataDirectory;
        var filePath = Path.Combine(directoryPath, fileName);
        
        using var fileStream = File.Create(filePath);
        await stream.CopyToAsync(fileStream);
        
        return filePath;
    }

    // 保存到公共图库(需配置平台权限)
    public static async Task SaveToGalleryAsync(ImageSource source, string fileName)
    {
        var streamSource = await ImageHelper.ConvertToStreamImageSourceAsync(source);
        using var stream = await streamSource.Stream(CancellationToken.None);
        
        // 先写入缓存目录,再通过MediaPicker保存到图库
        var cachePath = Path.Combine(FileSystem.CacheDirectory, fileName);
        using var cacheStream = File.Create(cachePath);
        await stream.CopyToAsync(cacheStream);
        
        await MediaPicker.Default.SavePhotoAsync(new MediaPickerSavePhotoOptions
        {
            FileName = fileName,
            SaveToAlbum = true
        });
    }
}

四、平台权限配置

Android

在AndroidManifest.xml中添加:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

iOS

在Info.plist中添加:

<key>NSPhotoLibraryAddUsageDescription</key>
<string>需要访问相册保存图片</string>

五、使用示例

// 原始图片源
var originalImage = ImageSource.FromFile("test.jpg");

// 旋转90度
var rotatedImage = await ImageHelper.RotateImageSourceAsync(originalImage, 90);

// 保存到应用私有目录
var savedPath = await ImageSaver.SaveToAppDirectoryAsync(rotatedImage, "rotated_test.jpg");

// 保存到公共图库
await ImageSaver.SaveToGalleryAsync(rotatedImage, "rotated_test.jpg");

内容的提问来源于stack exchange,提问作者LeandroTheDev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 00:22:20