From eb0751cb162a78b1dcc0a533437949ce86d17175 Mon Sep 17 00:00:00 2001 From: Ricardo Carneiro Date: Tue, 21 Oct 2025 17:02:26 -0300 Subject: [PATCH] feat: Tipos de qrcodes (bolinhas, arredondado ou quadrado) --- .claude/settings.local.json | 4 +- Services/QRRapidoService.cs | 219 ++++++++++++++++++++++++++++++- Views/Home/Index.cshtml | 2 - package-lock.json | 8 +- package.json | 2 +- wwwroot/js/qr-speed-generator.js | 10 +- 6 files changed, 230 insertions(+), 15 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index a0af5a0..f69a6ca 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -31,7 +31,9 @@ "Read(//mnt/c/vscode/**)", "Read(//mnt/c/**)", "Bash(chmod +x /mnt/c/vscode/qrrapido/Scripts/update-plans.sh)", - "Bash(netstat -tln)" + "Bash(netstat -tln)", + "Bash(npm install)", + "Bash(npm install:*)" ], "deny": [] } diff --git a/Services/QRRapidoService.cs b/Services/QRRapidoService.cs index eb7a0b1..e3f5495 100644 --- a/Services/QRRapidoService.cs +++ b/Services/QRRapidoService.cs @@ -679,21 +679,230 @@ namespace QRRapidoApp.Services private byte[] ApplyCornerStyle(byte[] qrBytes, string cornerStyle, int targetSize) { + if (cornerStyle == "square" || string.IsNullOrEmpty(cornerStyle)) + { + return qrBytes; // No processing needed for square style + } + try { - // Simplified implementation for cross-platform compatibility - // The complex corner styling can be re-implemented later using ImageSharp drawing primitives - _logger.LogInformation("Corner style '{CornerStyle}' temporarily disabled for cross-platform compatibility. Returning original QR code.", cornerStyle); - return qrBytes; + _logger.LogInformation("Applying corner style '{CornerStyle}' to QR code", cornerStyle); + + using var qrImage = Image.Load(qrBytes); + int width = qrImage.Width; + int height = qrImage.Height; + + // Detect module size by scanning the image + int moduleSize = DetectModuleSize(qrImage); + if (moduleSize <= 0) + { + _logger.LogWarning("Could not detect module size, returning original QR code"); + return qrBytes; + } + + _logger.LogDebug("Detected module size: {ModuleSize}px for {Width}x{Height} QR code", moduleSize, width, height); + + // Create a new image with styled modules + using var styledImage = new Image(width, height); + + // Process each module + for (int y = 0; y < height; y += moduleSize) + { + for (int x = 0; x < width; x += moduleSize) + { + // Check if this module is dark (part of the QR pattern) + var centerPixel = qrImage[x + moduleSize / 2, y + moduleSize / 2]; + bool isDark = centerPixel.R < 128; // Dark if RGB < 128 + + if (isDark) + { + // Draw styled module based on cornerStyle + DrawStyledModule(styledImage, x, y, moduleSize, cornerStyle, centerPixel); + } + else + { + // Draw background (light modules) + DrawRectangle(styledImage, x, y, moduleSize, moduleSize, centerPixel); + } + } + } + + // Convert back to bytes + using var outputStream = new MemoryStream(); + styledImage.SaveAsPng(outputStream); + _logger.LogInformation("Successfully applied corner style '{CornerStyle}'", cornerStyle); + 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(Image qrImage) + { + // Scan horizontally from left to find the first transition from light to dark + int width = qrImage.Width; + int height = qrImage.Height; + int midY = height / 2; + + bool wasLight = true; + int darkStart = -1; + int darkEnd = -1; + + for (int x = 0; x < width; x++) + { + var pixel = qrImage[x, midY]; + bool isLight = pixel.R >= 128; + + if (wasLight && !isLight) + { + // Transition from light to dark + darkStart = x; + } + else if (!wasLight && isLight && darkStart >= 0) + { + // Transition from dark to light + darkEnd = x; + break; + } + + wasLight = isLight; + } + + if (darkStart >= 0 && darkEnd > darkStart) + { + return darkEnd - darkStart; + } + + // Fallback: estimate based on typical QR code structure (21-177 modules) + // Most common is 25-29 modules for QR version 1-5 + return width / 25; // Rough estimate + } + + private void DrawStyledModule(Image image, int x, int y, int size, string style, Rgba32 color) + { + switch (style.ToLower()) + { + case "rounded": + DrawRoundedRectangle(image, x, y, size, size, size / 4, color); + break; + case "circle": + DrawCircle(image, x + size / 2, y + size / 2, size / 2, color); + break; + default: + DrawRectangle(image, x, y, size, size, color); + break; + } + } + + private void DrawRectangle(Image image, int x, int y, int width, int height, Rgba32 color) + { + for (int dy = 0; dy < height; dy++) + { + for (int dx = 0; dx < width; dx++) + { + int px = x + dx; + int py = y + dy; + if (px >= 0 && px < image.Width && py >= 0 && py < image.Height) + { + image[px, py] = color; + } + } + } + } + + private void DrawRoundedRectangle(Image image, int x, int y, int width, int height, int radius, Rgba32 color) + { + for (int dy = 0; dy < height; dy++) + { + for (int dx = 0; dx < width; dx++) + { + int px = x + dx; + int py = y + dy; + + if (px < 0 || px >= image.Width || py < 0 || py >= image.Height) + continue; + + // Check if pixel is inside rounded rectangle + bool inCorner = false; + int distX = 0, distY = 0; + + // Top-left corner + if (dx < radius && dy < radius) + { + distX = radius - dx; + distY = radius - dy; + inCorner = true; + } + // Top-right corner + else if (dx >= width - radius && dy < radius) + { + distX = dx - (width - radius - 1); + distY = radius - dy; + inCorner = true; + } + // Bottom-left corner + else if (dx < radius && dy >= height - radius) + { + distX = radius - dx; + distY = dy - (height - radius - 1); + inCorner = true; + } + // Bottom-right corner + else if (dx >= width - radius && dy >= height - radius) + { + distX = dx - (width - radius - 1); + distY = dy - (height - radius - 1); + inCorner = true; + } + + if (inCorner) + { + // Check if point is inside the rounded corner + double distance = Math.Sqrt(distX * distX + distY * distY); + if (distance <= radius) + { + image[px, py] = color; + } + } + else + { + // Inside the main rectangle (not in corners) + image[px, py] = color; + } + } + } + } + + private void DrawCircle(Image image, int centerX, int centerY, int radius, Rgba32 color) + { + int x0 = centerX - radius; + int y0 = centerY - radius; + int diameter = radius * 2; + + for (int dy = 0; dy <= diameter; dy++) + { + for (int dx = 0; dx <= diameter; dx++) + { + int px = x0 + dx; + int py = y0 + dy; + + if (px < 0 || px >= image.Width || py < 0 || py >= image.Height) + continue; + + // Calculate distance from center + double distance = Math.Sqrt(Math.Pow(dx - radius, 2) + Math.Pow(dy - radius, 2)); + + if (distance <= radius) + { + image[px, py] = color; + } + } + } + } + // QRModule class removed - was only used for corner styling which is temporarily simplified } } \ No newline at end of file diff --git a/Views/Home/Index.cshtml b/Views/Home/Index.cshtml index 9a77f8b..f9dd05a 100644 --- a/Views/Home/Index.cshtml +++ b/Views/Home/Index.cshtml @@ -718,13 +718,11 @@ { - } else { - } @if (!isPremium) diff --git a/package-lock.json b/package-lock.json index cc09cc7..7631e61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "qrrapido-app", "version": "1.0.0", "devDependencies": { - "vite": "^5.4.0" + "vite": "^5.4.21" } }, "node_modules/@esbuild/aix-ppc64": { @@ -879,9 +879,9 @@ } }, "node_modules/vite": { - "version": "5.4.20", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz", - "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==", + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 1c34846..bd81e28 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,6 @@ "preview": "vite preview --config vite.config.js" }, "devDependencies": { - "vite": "^5.4.0" + "vite": "^5.4.21" } } diff --git a/wwwroot/js/qr-speed-generator.js b/wwwroot/js/qr-speed-generator.js index f084cf6..80bb6be 100644 --- a/wwwroot/js/qr-speed-generator.js +++ b/wwwroot/js/qr-speed-generator.js @@ -1182,8 +1182,8 @@ class QRRapidoGenerator { handleCornerStyleChange(e) { const selectedStyle = e.target.value; - const premiumStyles = ['rounded', 'circle', 'leaf']; - + const premiumStyles = ['rounded', 'circle']; + if (premiumStyles.includes(selectedStyle)) { // Check if user is premium (we can detect this by checking if the option is disabled) const option = e.target.options[e.target.selectedIndex]; @@ -1194,6 +1194,12 @@ class QRRapidoGenerator { return; } } + + // If a QR code already exists, regenerate with new corner style + if (this.currentQR) { + console.log('[CORNER STYLE] Corner style changed to:', selectedStyle, '- regenerating QR code'); + this.generateQRWithTimer(e); + } } applyQuickStyle(e) {