feat: Tipos de qrcodes (bolinhas, arredondado ou quadrado)
All checks were successful
Deploy QR Rapido / test (push) Successful in 1m3s
Deploy QR Rapido / build-and-push (push) Successful in 8m23s
Deploy QR Rapido / deploy-staging (push) Has been skipped
Deploy QR Rapido / deploy-production (push) Successful in 3m34s

This commit is contained in:
Ricardo Carneiro 2025-10-21 17:02:26 -03:00
parent 8541e68711
commit eb0751cb16
6 changed files with 230 additions and 15 deletions

View File

@ -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": []
}

View File

@ -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<Rgba32>(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<Rgba32>(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<Rgba32> 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<Rgba32> 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<Rgba32> 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<Rgba32> 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<Rgba32> 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
}
}

View File

@ -718,13 +718,11 @@
{
<option value="rounded">@Localizer["Rounded"] 👑</option>
<option value="circle">Círculos 👑</option>
<option value="leaf">Folha 👑</option>
}
else
{
<option value="rounded" disabled>@Localizer["Rounded"] - Premium 👑</option>
<option value="circle" disabled>Círculos - Premium 👑</option>
<option value="leaf" disabled>Folha - Premium 👑</option>
}
</select>
@if (!isPremium)

8
package-lock.json generated
View File

@ -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": {

View File

@ -8,6 +8,6 @@
"preview": "vite preview --config vite.config.js"
},
"devDependencies": {
"vite": "^5.4.0"
"vite": "^5.4.21"
}
}

View File

@ -1182,7 +1182,7 @@ 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)
@ -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) {