using Microsoft.Extensions.Caching.Distributed; using QRCoder; using QRRapidoApp.Models.ViewModels; using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; using System.Security.Cryptography; using System.Text; using System.Text.Json; namespace QRRapidoApp.Services { public class QRRapidoService : IQRCodeService { private readonly IDistributedCache _cache; private readonly IConfiguration _config; private readonly ILogger _logger; private readonly SemaphoreSlim _semaphore; public QRRapidoService(IDistributedCache cache, IConfiguration config, ILogger logger) { _cache = cache; _config = config; _logger = logger; // Limit simultaneous generations to maintain performance var maxConcurrent = _config.GetValue("Performance:MaxConcurrentGenerations", 100); _semaphore = new SemaphoreSlim(maxConcurrent, maxConcurrent); } public async Task GenerateRapidAsync(QRGenerationRequest request) { var stopwatch = Stopwatch.StartNew(); try { await _semaphore.WaitAsync(); // Cache key based on content and settings var cacheKey = GenerateCacheKey(request); var cached = await _cache.GetStringAsync(cacheKey); if (!string.IsNullOrEmpty(cached)) { stopwatch.Stop(); _logger.LogInformation($"QR code served from cache in {stopwatch.ElapsedMilliseconds}ms"); var cachedResult = JsonSerializer.Deserialize(cached); if (cachedResult != null) { cachedResult.GenerationTimeMs = stopwatch.ElapsedMilliseconds; cachedResult.FromCache = true; return cachedResult; } } // Optimized generation var qrCode = await GenerateQRCodeOptimizedAsync(request); var base64 = Convert.ToBase64String(qrCode); var result = new QRGenerationResult { QRCodeBase64 = base64, QRId = Guid.NewGuid().ToString(), GenerationTimeMs = stopwatch.ElapsedMilliseconds, FromCache = false, Size = qrCode.Length, RequestSettings = request, Success = true }; // Cache for configurable time var cacheExpiration = TimeSpan.FromMinutes(_config.GetValue("Performance:CacheExpirationMinutes", 60)); await _cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(result), new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = cacheExpiration }); stopwatch.Stop(); _logger.LogInformation($"QR code generated in {stopwatch.ElapsedMilliseconds}ms for type {request.Type}"); return result; } catch (Exception ex) { _logger.LogError(ex, $"Error in rapid QR code generation: {ex.Message}"); return new QRGenerationResult { Success = false, ErrorMessage = ex.Message, GenerationTimeMs = stopwatch.ElapsedMilliseconds }; } finally { _semaphore.Release(); } } private async Task GenerateQRCodeOptimizedAsync(QRGenerationRequest request) { return await Task.Run(() => { using var qrGenerator = new QRCodeGenerator(); using var qrCodeData = qrGenerator.CreateQrCode(request.Content, GetErrorCorrectionLevel(request)); // Optimized settings for speed using var qrCode = new PngByteQRCode(qrCodeData); // Apply optimizations based on user type var pixelsPerModule = request.IsPremium ? GetOptimalPixelsPerModule(request.Size) : Math.Max(8, request.Size / 40); // Lower quality for free users, but faster var primaryColorBytes = ColorToBytes(ParseHtmlColor(request.PrimaryColor)); var backgroundColorBytes = ColorToBytes(ParseHtmlColor(request.BackgroundColor)); var qrBytes = qrCode.GetGraphic(pixelsPerModule, primaryColorBytes, backgroundColorBytes); // Apply custom corner styles for premium users if (request.IsPremium && !string.IsNullOrEmpty(request.CornerStyle) && request.CornerStyle != "square") { qrBytes = ApplyCornerStyle(qrBytes, request.CornerStyle, request.Size); } // Apply logo overlay if provided if (request.HasLogo && request.Logo != null) { return ApplyLogoOverlay(qrBytes, request.Logo, request.Size); } return qrBytes; }); } private QRCodeGenerator.ECCLevel GetErrorCorrectionLevel(QRGenerationRequest request) { // Lower error correction = faster generation if (request.OptimizeForSpeed) { return QRCodeGenerator.ECCLevel.L; // ~7% correction } return request.HasLogo ? QRCodeGenerator.ECCLevel.H : // ~30% correction for logos QRCodeGenerator.ECCLevel.M; // ~15% correction default } private int GetOptimalPixelsPerModule(int targetSize) { // Optimized algorithm for best quality/speed ratio return targetSize switch { <= 200 => 8, <= 300 => 12, <= 500 => 16, <= 800 => 20, _ => 24 }; } private string GenerateCacheKey(QRGenerationRequest request) { var keyData = $"{request.Content}|{request.Type}|{request.Size}|{request.PrimaryColor}|{request.BackgroundColor}|{request.QuickStyle}|{request.CornerStyle}|{request.Margin}"; using var sha256 = SHA256.Create(); var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(keyData)); return $"qr_rapid_{Convert.ToBase64String(hash)[..16]}"; } public async Task ConvertToSvgAsync(string qrCodeBase64) { return await Task.Run(() => { // Convert PNG to SVG (simplified implementation) var svgContent = $@" "; return Encoding.UTF8.GetBytes(svgContent); }); } public async Task ConvertToPdfAsync(string qrCodeBase64, int size = 300) { return await Task.Run(() => { // Simplified PDF generation - in real implementation, use iTextSharp or similar var pdfHeader = "%PDF-1.4\n"; var pdfBody = $"1 0 obj<>endobj\n" + $"2 0 obj<>endobj\n" + $"3 0 obj<>endobj\n" + $"xref\n0 4\n0000000000 65535 f \n0000000010 00000 n \n0000000053 00000 n \n0000000125 00000 n \n"; var pdfContent = pdfBody + $"trailer<>\nstartxref\n{pdfHeader.Length + pdfBody.Length}\n%%EOF"; return Encoding.UTF8.GetBytes(pdfHeader + pdfContent); }); } public async Task GenerateDynamicQRAsync(QRGenerationRequest request, string userId) { // For premium users only - dynamic QR codes that can be edited var dynamicId = Guid.NewGuid().ToString(); var dynamicUrl = $"https://qrrapido.site/d/{dynamicId}"; // Store mapping in cache/database var cacheKey = $"dynamic_qr_{dynamicId}"; await _cache.SetStringAsync(cacheKey, request.Content, new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromDays(365) // Long-lived for premium users }); return dynamicId; } public async Task UpdateDynamicQRAsync(string qrId, string newContent) { try { var cacheKey = $"dynamic_qr_{qrId}"; await _cache.SetStringAsync(cacheKey, newContent, new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromDays(365) }); return true; } catch (Exception ex) { _logger.LogError(ex, $"Failed to update dynamic QR {qrId}: {ex.Message}"); return false; } } private System.Drawing.Color ParseHtmlColor(string htmlColor) { if (string.IsNullOrEmpty(htmlColor) || !htmlColor.StartsWith("#")) { return System.Drawing.Color.Black; } try { if (htmlColor.Length == 7) // #RRGGBB { var r = Convert.ToByte(htmlColor.Substring(1, 2), 16); var g = Convert.ToByte(htmlColor.Substring(3, 2), 16); var b = Convert.ToByte(htmlColor.Substring(5, 2), 16); return System.Drawing.Color.FromArgb(r, g, b); } else if (htmlColor.Length == 4) // #RGB { var r = Convert.ToByte(htmlColor.Substring(1, 1) + htmlColor.Substring(1, 1), 16); var g = Convert.ToByte(htmlColor.Substring(2, 1) + htmlColor.Substring(2, 1), 16); var b = Convert.ToByte(htmlColor.Substring(3, 1) + htmlColor.Substring(3, 1), 16); return System.Drawing.Color.FromArgb(r, g, b); } } catch { return System.Drawing.Color.Black; } return System.Drawing.Color.Black; } private byte[] ColorToBytes(System.Drawing.Color color) { return new byte[] { color.R, color.G, color.B }; } private byte[] ApplyLogoOverlay(byte[] qrBytes, byte[] logoBytes, int qrSize) { try { using var qrStream = new MemoryStream(qrBytes); using var logoStream = new MemoryStream(logoBytes); using var qrImage = new Bitmap(qrStream); using var logoImage = new Bitmap(logoStream); // Create a new bitmap to draw on (to avoid modifying the original) using var finalImage = new Bitmap(qrImage.Width, qrImage.Height); using var graphics = Graphics.FromImage(finalImage); // Set high quality rendering graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; // Draw the QR code as base graphics.DrawImage(qrImage, 0, 0, qrImage.Width, qrImage.Height); // Calculate logo size (20% of QR code size) var logoSize = Math.Min(qrImage.Width, qrImage.Height) / 5; // Calculate center position var logoX = (qrImage.Width - logoSize) / 2; var logoY = (qrImage.Height - logoSize) / 2; // Create a white background circle for better contrast var backgroundSize = logoSize + 10; // Slightly larger than logo var backgroundX = (qrImage.Width - backgroundSize) / 2; var backgroundY = (qrImage.Height - backgroundSize) / 2; using var whiteBrush = new SolidBrush(System.Drawing.Color.White); graphics.FillEllipse(whiteBrush, backgroundX, backgroundY, backgroundSize, backgroundSize); // Draw the logo graphics.DrawImage(logoImage, logoX, logoY, logoSize, logoSize); // Convert back to byte array using var outputStream = new MemoryStream(); finalImage.Save(outputStream, ImageFormat.Png); return outputStream.ToArray(); } catch (Exception ex) { _logger.LogError(ex, "Error applying logo overlay, returning original QR code"); // Return original QR code if logo overlay fails return qrBytes; } } private byte[] ApplyCornerStyle(byte[] qrBytes, string cornerStyle, int targetSize) { try { using var originalStream = new MemoryStream(qrBytes); using var originalImage = new Bitmap(originalStream); using var styledImage = new Bitmap(originalImage.Width, originalImage.Height); using var graphics = Graphics.FromImage(styledImage); // Set high quality rendering graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; // Fill with background color first graphics.Clear(System.Drawing.Color.White); // Analyze the QR code to identify modules var moduleSize = DetectModuleSize(originalImage); var modules = ExtractQRModules(originalImage, moduleSize); // Draw modules with custom style foreach (var module in modules) { if (module.IsBlack) { DrawStyledModule(graphics, module, cornerStyle, moduleSize); } } // Convert back to byte array using var outputStream = new MemoryStream(); styledImage.Save(outputStream, ImageFormat.Png); return outputStream.ToArray(); } catch (Exception ex) { _logger.LogError(ex, "Error applying corner style {CornerStyle}, returning original QR code", cornerStyle); // Return original QR code if styling fails return qrBytes; } } private int DetectModuleSize(Bitmap qrImage) { // Simple detection: scan from top-left to find first module boundaries // QR codes typically have quiet zones, so we look for the first black pixel pattern for (int size = 4; size <= 20; size++) { if (IsValidModuleSize(qrImage, size)) { return size; } } return 8; // Fallback } private bool IsValidModuleSize(Bitmap image, int moduleSize) { // Simple validation: check if the suspected module size creates a reasonable grid var expectedModules = image.Width / moduleSize; return expectedModules >= 21 && expectedModules <= 177 && (expectedModules % 4 == 1); } private List ExtractQRModules(Bitmap image, int moduleSize) { var modules = new List(); var modulesPerRow = image.Width / moduleSize; for (int row = 0; row < modulesPerRow; row++) { for (int col = 0; col < modulesPerRow; col++) { var x = col * moduleSize + moduleSize / 2; var y = row * moduleSize + moduleSize / 2; if (x < image.Width && y < image.Height) { var pixel = image.GetPixel(x, y); var isBlack = pixel.R < 128; // Simple threshold modules.Add(new QRModule { X = col * moduleSize, Y = row * moduleSize, Size = moduleSize, IsBlack = isBlack }); } } } return modules; } private void DrawStyledModule(Graphics graphics, QRModule module, string style, int moduleSize) { using var brush = new SolidBrush(System.Drawing.Color.Black); switch (style.ToLower()) { case "rounded": // Draw rounded rectangles var roundedRect = new Rectangle(module.X, module.Y, moduleSize, moduleSize); using (var path = CreateRoundedRectPath(roundedRect, moduleSize / 4)) { graphics.FillPath(brush, path); } break; case "circle": case "dots": // Draw circles/dots var margin = moduleSize / 6; graphics.FillEllipse(brush, module.X + margin, module.Y + margin, moduleSize - 2 * margin, moduleSize - 2 * margin); break; case "leaf": // Draw leaf-shaped modules (rounded on one side) DrawLeafModule(graphics, brush, module, moduleSize); break; default: // Square (fallback) graphics.FillRectangle(brush, module.X, module.Y, moduleSize, moduleSize); break; } } private System.Drawing.Drawing2D.GraphicsPath CreateRoundedRectPath(Rectangle rect, int radius) { var path = new System.Drawing.Drawing2D.GraphicsPath(); path.AddArc(rect.X, rect.Y, radius * 2, radius * 2, 180, 90); path.AddArc(rect.Right - radius * 2, rect.Y, radius * 2, radius * 2, 270, 90); path.AddArc(rect.Right - radius * 2, rect.Bottom - radius * 2, radius * 2, radius * 2, 0, 90); path.AddArc(rect.X, rect.Bottom - radius * 2, radius * 2, radius * 2, 90, 90); path.CloseFigure(); return path; } private void DrawLeafModule(Graphics graphics, SolidBrush brush, QRModule module, int moduleSize) { // Create a path that looks like a leaf (rounded on top-right, square elsewhere) using var path = new System.Drawing.Drawing2D.GraphicsPath(); var rect = new Rectangle(module.X, module.Y, moduleSize, moduleSize); var radius = moduleSize / 3; // Start from top-left, go clockwise path.AddLine(rect.X, rect.Y, rect.Right - radius, rect.Y); path.AddArc(rect.Right - radius * 2, rect.Y, radius * 2, radius * 2, 270, 90); path.AddLine(rect.Right, rect.Y + radius, rect.Right, rect.Bottom); path.AddLine(rect.Right, rect.Bottom, rect.X, rect.Bottom); path.AddLine(rect.X, rect.Bottom, rect.X, rect.Y); path.CloseFigure(); graphics.FillPath(brush, path); } private class QRModule { public int X { get; set; } public int Y { get; set; } public int Size { get; set; } public bool IsBlack { get; set; } } } }