using Microsoft.Extensions.Localization; using QRRapidoApp.Models; using QRRapidoApp.Models.ViewModels; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using System.Collections.Generic; namespace QRRapidoApp.Services { public class LogoReadabilityAnalyzer { private readonly ILogger _logger; private readonly IStringLocalizer _localizer; public LogoReadabilityAnalyzer(ILogger logger, IStringLocalizer localizer) { _logger = logger; _localizer = localizer; } public async Task AnalyzeAsync(QRGenerationRequest request, int qrSize = 300) { var result = new LogoReadabilityInfo(); if (!request.HasLogo || request.Logo == null || request.Logo.Length == 0) { result.HasLogo = false; result.ReadabilityScore = 100; result.DifficultyLevel = ReadabilityLevel.VeryEasy.ToString(); result.UserMessage = "✅ Perfeita legibilidade! QR code sem logo é sempre facilmente lido."; return result; } try { result.HasLogo = true; result.LogoComplexity = await AnalyzeLogoComplexityAsync(request.Logo); result.LogoSizePercent = request.LogoSizePercent ?? 20; // Calcular score baseado em múltiplos fatores result.ReadabilityScore = CalculateReadabilityScore(result.LogoComplexity, result.LogoSizePercent, qrSize); result.DifficultyLevel = GetDifficultyLevel(result.ReadabilityScore).ToString(); result.UserMessage = GenerateUserMessage(result.ReadabilityScore, result.LogoSizePercent); result.Tips = GeneratePersonalizedTips(result.LogoComplexity, result.LogoSizePercent, result.ReadabilityScore); _logger.LogInformation("Logo readability analysis completed - Score: {Score}, Size: {Size}%, Level: {Level}", result.ReadabilityScore, result.LogoSizePercent, result.DifficultyLevel); return result; } catch (Exception ex) { _logger.LogError(ex, "Error analyzing logo readability"); // Fallback para análise básica baseada apenas no tamanho result.HasLogo = true; result.LogoSizePercent = request.LogoSizePercent ?? 20; result.ReadabilityScore = Math.Max(40, 100 - (result.LogoSizePercent - 15) * 3); result.DifficultyLevel = GetDifficultyLevel(result.ReadabilityScore).ToString(); result.UserMessage = GenerateUserMessage(result.ReadabilityScore, result.LogoSizePercent); result.Tips = new List { "⚠️ Análise limitada devido a erro técnico. Teste sempre em vários dispositivos." }; return result; } } private async Task AnalyzeLogoComplexityAsync(byte[] logoBytes) { return await Task.Run(() => { try { using var stream = new MemoryStream(logoBytes); using var image = Image.Load(stream); var complexity = new LogoComplexity { Width = image.Width, Height = image.Height, IsSquare = Math.Abs(image.Width - image.Height) <= Math.Min(image.Width, image.Height) * 0.1, FileSizeBytes = logoBytes.Length, FileFormat = DetectImageFormat(logoBytes) }; // Analisar transparência complexity.HasTransparency = HasTransparency(image); // Analisar complexidade de cores var colorAnalysis = AnalyzeColors(image); complexity.DominantColorCount = colorAnalysis.dominantColors; complexity.ContrastRatio = colorAnalysis.contrastRatio; _logger.LogDebug("Logo complexity analysis - {Width}x{Height}, Square: {IsSquare}, Transparency: {HasTransparency}, Colors: {Colors}", complexity.Width, complexity.Height, complexity.IsSquare, complexity.HasTransparency, complexity.DominantColorCount); return complexity; } catch (Exception ex) { _logger.LogError(ex, "Error analyzing logo complexity"); return new LogoComplexity { Width = 100, Height = 100, IsSquare = true, HasTransparency = false, DominantColorCount = 5, ContrastRatio = 0.5, FileFormat = "unknown", FileSizeBytes = logoBytes.Length }; } }); } private int CalculateReadabilityScore(LogoComplexity complexity, int logoSizePercent, int qrSize) { int score = 100; // Base score // Penalização por tamanho (fator mais importante) if (logoSizePercent > 20) { score -= (logoSizePercent - 20) * 2; // -2 pontos por % acima de 20% } // Penalização por complexidade de cores if (complexity.DominantColorCount > 5) { score -= (complexity.DominantColorCount - 5) * 3; // -3 pontos por cor extra } // Penalização por baixo contraste if (complexity.ContrastRatio < 0.3) { score -= 10; // Contraste muito baixo } // Bonificações if (complexity.HasTransparency) { score += 5; // PNG com transparência é melhor } if (complexity.IsSquare) { score += 5; // Logo quadrado funciona melhor } if (qrSize >= 500) { score += 5; // QR codes maiores facilitam leitura } // Penalização por logo muito grande em pixels (pode ser mais difícil de processar) if (complexity.Width > 800 || complexity.Height > 800) { score -= 5; } // Garantir que o score fique entre 0-100 return Math.Max(0, Math.Min(100, score)); } private ReadabilityLevel GetDifficultyLevel(int score) { return score switch { >= 85 => ReadabilityLevel.VeryEasy, >= 70 => ReadabilityLevel.Easy, >= 55 => ReadabilityLevel.Medium, >= 40 => ReadabilityLevel.Hard, _ => ReadabilityLevel.VeryHard }; } private string GenerateUserMessage(int score, int logoSizePercent) { return score switch { >= 85 => $"✅ Excelente legibilidade! Logo de {logoSizePercent}% será facilmente lido por qualquer celular.", >= 70 => $"🟢 Boa legibilidade! Logo de {logoSizePercent}% deve funcionar bem na maioria dos dispositivos.", >= 55 => $"⚠️ Legibilidade moderada. Logo de {logoSizePercent}% pode exigir melhor iluminação ou posicionamento.", >= 40 => $"🟡 Legibilidade baixa. Logo de {logoSizePercent}% pode ser difícil de ler. Considere reduzir para melhor resultado.", _ => $"🔴 Legibilidade muito baixa. Logo de {logoSizePercent}% provavelmente será difícil de escanear. Recomendamos reduzir significativamente." }; } private List GeneratePersonalizedTips(LogoComplexity complexity, int logoSizePercent, int score) { var tips = new List(); // Dicas básicas sempre presentes tips.Add("📱 Teste sempre em vários apps de QR Code diferentes"); // Dicas baseadas no score if (score < 70) { tips.Add("💡 Use boa iluminação ao escanear para melhor resultado"); } // Dicas baseadas no tamanho if (logoSizePercent > 22) { tips.Add($"📏 Considere reduzir logo para 18-20% (atual: {logoSizePercent}%)"); } // Dicas baseadas na complexidade if (!complexity.HasTransparency) { tips.Add("🎨 PNG com fundo transparente melhora a legibilidade"); } if (!complexity.IsSquare) { tips.Add("🔲 Logos quadrados funcionam melhor que retangulares"); } if (complexity.DominantColorCount > 6) { tips.Add("🌈 Logos com menos cores (2-4) são mais fáceis de ler"); } if (complexity.ContrastRatio < 0.4) { tips.Add("⚡ Aumente o contraste do logo para melhor visibilidade"); } // Dica sobre tamanho do QR tips.Add("📐 QR Codes maiores (500px+) facilitam leitura com logo"); // Dica final baseada no nível de dificuldade if (score < 55) { tips.Add("🔧 Para máxima compatibilidade, considere versão sem logo para impressões pequenas"); } return tips; } private bool HasTransparency(Image image) { try { // Verificar se há pixels com alpha < 255 (transparência) var hasTransparency = false; image.ProcessPixelRows(accessor => { for (int y = 0; y < accessor.Height && !hasTransparency; y++) { var pixelRow = accessor.GetRowSpan(y); for (int x = 0; x < pixelRow.Length && !hasTransparency; x++) { if (pixelRow[x].A < 255) { hasTransparency = true; } } } }); return hasTransparency; } catch { return false; // Assume não transparente se der erro } } private (int dominantColors, double contrastRatio) AnalyzeColors(Image image) { try { var colorCounts = new Dictionary(); var totalPixels = 0; var brightPixels = 0; // Amostragem para performance (analisa a cada 4 pixels) image.ProcessPixelRows(accessor => { for (int y = 0; y < accessor.Height; y += 4) { var pixelRow = accessor.GetRowSpan(y); for (int x = 0; x < pixelRow.Length; x += 4) { var pixel = pixelRow[x]; // Ignorar pixels totalmente transparentes if (pixel.A < 128) continue; // Quantizar cores para reduzir variações var quantizedColor = QuantizeColor(pixel); colorCounts[quantizedColor] = colorCounts.GetValueOrDefault(quantizedColor, 0) + 1; // Calcular brilho para contraste var luminance = (pixel.R * 0.299 + pixel.G * 0.587 + pixel.B * 0.114) / 255.0; if (luminance > 0.5) brightPixels++; totalPixels++; } } }); var dominantColors = Math.Min(colorCounts.Count, 10); // Limitar a 10 cores dominantes var contrastRatio = totalPixels > 0 ? (double)brightPixels / totalPixels : 0.5; return (dominantColors, contrastRatio); } catch { return (5, 0.5); // Valores padrão em caso de erro } } private uint QuantizeColor(Rgba32 color) { // Quantizar cores para 32 níveis (reduz variações mínimas) var r = (byte)((color.R / 8) * 8); var g = (byte)((color.G / 8) * 8); var b = (byte)((color.B / 8) * 8); return (uint)(r << 16 | g << 8 | b); } private string DetectImageFormat(byte[] imageBytes) { if (imageBytes.Length < 4) return "unknown"; // PNG signature if (imageBytes[0] == 0x89 && imageBytes[1] == 0x50 && imageBytes[2] == 0x4E && imageBytes[3] == 0x47) return "PNG"; // JPEG signature if (imageBytes[0] == 0xFF && imageBytes[1] == 0xD8) return "JPEG"; // GIF signature if (imageBytes[0] == 0x47 && imageBytes[1] == 0x49 && imageBytes[2] == 0x46) return "GIF"; // WebP signature if (imageBytes.Length > 12 && imageBytes[0] == 0x52 && imageBytes[1] == 0x49 && imageBytes[2] == 0x46 && imageBytes[3] == 0x46 && imageBytes[8] == 0x57 && imageBytes[9] == 0x45 && imageBytes[10] == 0x42 && imageBytes[11] == 0x50) return "WebP"; return "unknown"; } } }