2025-10-11 18:25:59 +08:00

247 lines
9.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.IO;
using System.Text;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Maui.Media;
using XiaoZhiSharp_MauiApp.Services;
using AndroidX.Core.Content;
using Android.Content;
using Android.Content.PM;
using Android.Graphics;
namespace XiaoZhiSharp_MauiApp.Platforms.Android.Services
{
public class CameraService : ICameraService
{
private readonly HttpClient _httpClient;
private string _explainUrl = "";
private string _explainToken = "";
public CameraService()
{
_httpClient = new HttpClient();
}
public bool IsSupported => MediaPicker.Default.IsCaptureSupported;
public bool HasPermission
{
get
{
var context = Platform.CurrentActivity?.ApplicationContext ?? global::Android.App.Application.Context;
return ContextCompat.CheckSelfPermission(context, global::Android.Manifest.Permission.Camera) == Permission.Granted;
}
}
public event EventHandler<bool>? CameraStatusChanged;
public async Task<bool> RequestPermissionAsync()
{
try
{
var status = await Permissions.RequestAsync<Permissions.Camera>();
var hasPermission = status == PermissionStatus.Granted;
CameraStatusChanged?.Invoke(this, hasPermission);
return hasPermission;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"请求摄像头权限时出错: {ex.Message}");
return false;
}
}
public async Task<byte[]?> CapturePhotoAsync()
{
try
{
if (!HasPermission)
{
var granted = await RequestPermissionAsync();
if (!granted)
{
System.Diagnostics.Debug.WriteLine("没有摄像头权限");
return null;
}
}
var photo = await MediaPicker.Default.CapturePhotoAsync(new MediaPickerOptions
{
Title = "拍照进行AI识别"
});
if (photo == null)
return null;
// 读取图片数据
using var sourceStream = await photo.OpenReadAsync();
using var memoryStream = new MemoryStream();
await sourceStream.CopyToAsync(memoryStream);
var originalSize = memoryStream.Length;
System.Diagnostics.Debug.WriteLine($"拍照成功,原始图片大小: {originalSize} bytes");
// 压缩图片
var compressedData = CompressImage(memoryStream.ToArray());
System.Diagnostics.Debug.WriteLine($"图片压缩后大小: {compressedData.Length} bytes (压缩比: {(double)compressedData.Length / originalSize:P2})");
return compressedData;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"拍照时出错: {ex.Message}");
return null;
}
}
private byte[] CompressImage(byte[] imageData, int maxWidth = 800, int maxHeight = 600, int quality = 80)
{
try
{
using var originalBitmap = BitmapFactory.DecodeByteArray(imageData, 0, imageData.Length);
if (originalBitmap == null)
{
System.Diagnostics.Debug.WriteLine("无法解码图片,返回原数据");
return imageData;
}
// 计算缩放比例
float scaleWidth = (float)maxWidth / originalBitmap.Width;
float scaleHeight = (float)maxHeight / originalBitmap.Height;
float scaleFactor = Math.Min(scaleWidth, scaleHeight);
// 如果图片已经很小,不需要缩放
if (scaleFactor >= 1.0f)
{
scaleFactor = 1.0f;
}
int newWidth = (int)(originalBitmap.Width * scaleFactor);
int newHeight = (int)(originalBitmap.Height * scaleFactor);
System.Diagnostics.Debug.WriteLine($"图片缩放: {originalBitmap.Width}x{originalBitmap.Height} -> {newWidth}x{newHeight} (比例: {scaleFactor:F2})");
// 创建缩放后的位图
using var scaledBitmap = Bitmap.CreateScaledBitmap(originalBitmap, newWidth, newHeight, true);
// 压缩为JPEG
using var stream = new MemoryStream();
scaledBitmap.Compress(Bitmap.CompressFormat.Jpeg, quality, stream);
return stream.ToArray();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"压缩图片时出错: {ex.Message}");
return imageData; // 返回原数据
}
}
public async Task<string> CaptureAndExplainAsync(string question)
{
var imageData = await CapturePhotoAsync();
if (imageData == null)
{
return "{\"success\": false, \"message\": \"拍照失败\"}";
}
return await ExplainImageAsync(imageData, question);
}
public async Task<string> ExplainImageAsync(byte[] imageData, string question)
{
try
{
if (string.IsNullOrEmpty(_explainUrl))
{
return "{\"success\": false, \"message\": \"AI识别服务URL未设置\"}";
}
if (imageData == null || imageData.Length == 0)
{
return "{\"success\": false, \"message\": \"图像数据无效\"}";
}
System.Diagnostics.Debug.WriteLine($"开始AI识别问题: {question},图像大小: {imageData.Length} bytes");
// 构造multipart/form-data请求
using var content = new MultipartFormDataContent();
// 添加问题字段
content.Add(new StringContent(question), "question");
// 添加图片数据
var imageContent = new ByteArrayContent(imageData);
imageContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg");
content.Add(imageContent, "file", "camera.jpg");
// 设置请求头
var request = new HttpRequestMessage(HttpMethod.Post, _explainUrl)
{
Content = content
};
// 添加设备标识
request.Headers.Add("Device-Id", GetDeviceId());
request.Headers.Add("Client-Id", GetClientId());
if (!string.IsNullOrEmpty(_explainToken))
{
request.Headers.Add("Authorization", $"Bearer {_explainToken}");
}
var response = await _httpClient.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsStringAsync();
System.Diagnostics.Debug.WriteLine($"AI识别结果: {result}");
return result;
}
else
{
var errorBody = await response.Content.ReadAsStringAsync();
System.Diagnostics.Debug.WriteLine($"AI识别失败状态码: {response.StatusCode},响应: {errorBody}");
return $"{{\"success\": false, \"message\": \"AI识别失败状态码: {response.StatusCode}\"}}";
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"AI识别时出错: {ex.Message}");
return $"{{\"success\": false, \"message\": \"AI识别时出错: {ex.Message}\"}}";
}
}
public void SetExplainUrl(string url, string token)
{
_explainUrl = url;
_explainToken = token;
System.Diagnostics.Debug.WriteLine($"设置AI识别服务URL: {url}");
}
private string GetDeviceId()
{
try
{
return global::Android.Provider.Settings.Secure.GetString(
Platform.CurrentActivity?.ContentResolver ?? global::Android.App.Application.Context.ContentResolver,
global::Android.Provider.Settings.Secure.AndroidId) ?? "unknown";
}
catch
{
return "unknown";
}
}
private string GetClientId()
{
return Guid.NewGuid().ToString();
}
public void Dispose()
{
_httpClient?.Dispose();
}
}
}