fix: gerar qrcode com logo e com imagens de diferentes tamanhos.
This commit is contained in:
parent
3fa95aefd8
commit
9b094ed712
@ -12,7 +12,16 @@
|
|||||||
"Bash(pkill:*)",
|
"Bash(pkill:*)",
|
||||||
"Bash(true)",
|
"Bash(true)",
|
||||||
"Bash(dotnet clean:*)",
|
"Bash(dotnet clean:*)",
|
||||||
"Bash(grep:*)"
|
"Bash(grep:*)",
|
||||||
|
"Bash(ss:*)",
|
||||||
|
"Bash(killall:*)",
|
||||||
|
"Bash(node:*)",
|
||||||
|
"Bash(jshint:*)",
|
||||||
|
"Bash(ls:*)",
|
||||||
|
"Bash(convert:*)",
|
||||||
|
"Bash(dotnet add package:*)",
|
||||||
|
"Bash(dotnet remove package:*)",
|
||||||
|
"Bash(dotnet restore:*)"
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
}
|
}
|
||||||
|
|||||||
@ -378,8 +378,8 @@ namespace QRRapidoApp.Controllers
|
|||||||
request.Logo = memoryStream.ToArray();
|
request.Logo = memoryStream.ToArray();
|
||||||
request.HasLogo = true;
|
request.HasLogo = true;
|
||||||
|
|
||||||
_logger.LogInformation("Logo processed successfully - Size: {LogoSize} bytes, Format: {ContentType}",
|
_logger.LogInformation("Logo processed successfully - Size: {LogoSize} bytes, Format: {ContentType}, SizePercent: {SizePercent}%, Colorized: {Colorized}",
|
||||||
logo.Length, logo.ContentType);
|
logo.Length, logo.ContentType, request.LogoSizePercent ?? 20, request.ApplyLogoColorization);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -406,8 +406,8 @@ namespace QRRapidoApp.Controllers
|
|||||||
request.IsPremium = user?.IsPremium == true;
|
request.IsPremium = user?.IsPremium == true;
|
||||||
request.OptimizeForSpeed = true;
|
request.OptimizeForSpeed = true;
|
||||||
|
|
||||||
_logger.LogDebug("Generating QR code with logo - IsPremium: {IsPremium}, HasLogo: {HasLogo}",
|
_logger.LogDebug("Generating QR code with logo - IsPremium: {IsPremium}, HasLogo: {HasLogo}, LogoSize: {LogoSize}%, Colorized: {Colorized}",
|
||||||
request.IsPremium, request.HasLogo);
|
request.IsPremium, request.HasLogo, request.LogoSizePercent ?? 20, request.ApplyLogoColorization);
|
||||||
|
|
||||||
// Generate QR code
|
// Generate QR code
|
||||||
var generationStopwatch = Stopwatch.StartNew();
|
var generationStopwatch = Stopwatch.StartNew();
|
||||||
@ -421,8 +421,8 @@ namespace QRRapidoApp.Controllers
|
|||||||
return StatusCode(500, new { error = result.ErrorMessage, success = false });
|
return StatusCode(500, new { error = result.ErrorMessage, success = false });
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("QR code with logo generated successfully - GenerationTime: {GenerationTimeMs}ms, FromCache: {FromCache}, HasLogo: {HasLogo}",
|
_logger.LogInformation("QR code with logo generated successfully - GenerationTime: {GenerationTimeMs}ms, FromCache: {FromCache}, HasLogo: {HasLogo}, Base64Length: {Base64Length}",
|
||||||
generationStopwatch.ElapsedMilliseconds, result.FromCache, request.HasLogo);
|
generationStopwatch.ElapsedMilliseconds, result.FromCache, request.HasLogo, result.QRCodeBase64?.Length ?? 0);
|
||||||
|
|
||||||
// Save to history if user is logged in (fire and forget)
|
// Save to history if user is logged in (fire and forget)
|
||||||
if (userId != null)
|
if (userId != null)
|
||||||
@ -442,6 +442,7 @@ namespace QRRapidoApp.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
stopwatch.Stop();
|
stopwatch.Stop();
|
||||||
|
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@ -15,6 +15,17 @@ namespace QRRapidoApp.Models.ViewModels
|
|||||||
public bool IsPremium { get; set; } = false;
|
public bool IsPremium { get; set; } = false;
|
||||||
public bool HasLogo { get; set; } = false;
|
public bool HasLogo { get; set; } = false;
|
||||||
public byte[]? Logo { get; set; }
|
public byte[]? Logo { get; set; }
|
||||||
|
|
||||||
|
// NOVAS PROPRIEDADES PARA LOGO APRIMORADO
|
||||||
|
/// <summary>
|
||||||
|
/// Tamanho do logo em porcentagem (10-25%). Padrão: 20%
|
||||||
|
/// </summary>
|
||||||
|
public int? LogoSizePercent { get; set; } = 20;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Se deve aplicar a cor do QR code no logo (Premium feature)
|
||||||
|
/// </summary>
|
||||||
|
public bool ApplyLogoColorization { get; set; } = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class QRGenerationResult
|
public class QRGenerationResult
|
||||||
|
|||||||
@ -21,10 +21,11 @@
|
|||||||
<PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" />
|
<PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.1-dev-00953" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.1-dev-00953" />
|
||||||
<PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0" />
|
<PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0" />
|
||||||
|
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.6" />
|
||||||
|
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.4" />
|
||||||
<PackageReference Include="Stripe.net" Version="48.4.0" />
|
<PackageReference Include="Stripe.net" Version="48.4.0" />
|
||||||
<PackageReference Include="StackExchange.Redis" Version="2.7.4" />
|
<PackageReference Include="StackExchange.Redis" Version="2.7.4" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.0" />
|
||||||
<PackageReference Include="System.Drawing.Common" Version="8.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Localization" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Localization" Version="2.2.0" />
|
||||||
<PackageReference Include="xunit.assert" Version="2.9.3" />
|
<PackageReference Include="xunit.assert" Version="2.9.3" />
|
||||||
|
|||||||
@ -2,8 +2,12 @@ using Microsoft.Extensions.Caching.Distributed;
|
|||||||
using QRCoder;
|
using QRCoder;
|
||||||
using QRRapidoApp.Models.ViewModels;
|
using QRRapidoApp.Models.ViewModels;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Drawing;
|
using SixLabors.ImageSharp;
|
||||||
using System.Drawing.Imaging;
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
using SixLabors.ImageSharp.Formats.Png;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using SixLabors.ImageSharp.Drawing.Processing;
|
||||||
|
using System.Numerics;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
@ -102,48 +106,182 @@ namespace QRRapidoApp.Services
|
|||||||
return await Task.Run(() =>
|
return await Task.Run(() =>
|
||||||
{
|
{
|
||||||
using var qrGenerator = new QRCodeGenerator();
|
using var qrGenerator = new QRCodeGenerator();
|
||||||
using var qrCodeData = qrGenerator.CreateQrCode(request.Content, GetErrorCorrectionLevel(request));
|
|
||||||
|
|
||||||
// Optimized settings for speed
|
// CORREÇÃO 1: Usar nível de correção HIGH para logos
|
||||||
|
var errorCorrectionLevel = request.HasLogo && request.Logo != null ?
|
||||||
|
QRCodeGenerator.ECCLevel.H : // 30% correção para logos
|
||||||
|
QRCodeGenerator.ECCLevel.M; // 15% correção normal
|
||||||
|
|
||||||
|
using var qrCodeData = qrGenerator.CreateQrCode(request.Content, errorCorrectionLevel);
|
||||||
|
|
||||||
|
// CORREÇÃO 2: Usar PngByteQRCode para compatibilidade
|
||||||
using var qrCode = new PngByteQRCode(qrCodeData);
|
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 primaryColorBytes = ColorToBytes(ParseHtmlColor(request.PrimaryColor));
|
||||||
var backgroundColorBytes = ColorToBytes(ParseHtmlColor(request.BackgroundColor));
|
var backgroundColorBytes = ColorToBytes(ParseHtmlColor(request.BackgroundColor));
|
||||||
|
|
||||||
var qrBytes = qrCode.GetGraphic(pixelsPerModule, primaryColorBytes, backgroundColorBytes);
|
var pixelsPerModule = request.IsPremium ?
|
||||||
|
GetOptimalPixelsPerModule(request.Size) :
|
||||||
|
Math.Max(8, request.Size / 40);
|
||||||
|
|
||||||
// Apply custom corner styles for premium users
|
byte[] qrBytes;
|
||||||
|
|
||||||
|
// CORREÇÃO 3: Para logos, usar implementação otimizada com alta correção de erro
|
||||||
|
if (request.HasLogo && request.Logo != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Validar logo primeiro
|
||||||
|
if (!ValidateLogoForQR(request.Logo, out string errorMessage))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Logo validation failed: {ErrorMessage}, generating QR without logo", errorMessage);
|
||||||
|
// Fallback para QR sem logo
|
||||||
|
qrBytes = qrCode.GetGraphic(pixelsPerModule, primaryColorBytes, backgroundColorBytes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// NOVO: Preprocessar logo (redimensionar se necessário)
|
||||||
|
var processedLogo = PreprocessLogo(request.Logo);
|
||||||
|
|
||||||
|
// Gerar QR base com alta correção de erro
|
||||||
|
qrBytes = qrCode.GetGraphic(pixelsPerModule, primaryColorBytes, backgroundColorBytes);
|
||||||
|
|
||||||
|
// CORREÇÃO 4: Aplicar logo processado com configurações aprimoradas
|
||||||
|
qrBytes = ApplyLogoOverlayEnhanced(qrBytes, processedLogo, request);
|
||||||
|
|
||||||
|
_logger.LogInformation("QR code with logo generated - Size: {LogoSize}%, Colorized: {IsColorized}",
|
||||||
|
request.LogoSizePercent ?? 20, request.ApplyLogoColorization);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error applying logo, falling back to no logo");
|
||||||
|
// Fallback para QR sem logo se der erro
|
||||||
|
qrBytes = qrCode.GetGraphic(pixelsPerModule, primaryColorBytes, backgroundColorBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// QR sem logo - usar correção padrão
|
||||||
|
qrBytes = qrCode.GetGraphic(pixelsPerModule, primaryColorBytes, backgroundColorBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORREÇÃO 5: Aplicar estilos de canto APÓS o logo (se necessário)
|
||||||
if (request.IsPremium && !string.IsNullOrEmpty(request.CornerStyle) && request.CornerStyle != "square")
|
if (request.IsPremium && !string.IsNullOrEmpty(request.CornerStyle) && request.CornerStyle != "square")
|
||||||
{
|
{
|
||||||
qrBytes = ApplyCornerStyle(qrBytes, request.CornerStyle, request.Size);
|
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;
|
return qrBytes;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private QRCodeGenerator.ECCLevel GetErrorCorrectionLevel(QRGenerationRequest request)
|
private QRCodeGenerator.ECCLevel GetErrorCorrectionLevel(QRGenerationRequest request)
|
||||||
{
|
{
|
||||||
// Lower error correction = faster generation
|
// Para logos, sempre usar correção HIGH
|
||||||
if (request.OptimizeForSpeed)
|
if (request.HasLogo && request.Logo != null)
|
||||||
{
|
{
|
||||||
return QRCodeGenerator.ECCLevel.L; // ~7% correction
|
_logger.LogInformation("Using HIGH error correction level for logo QR code");
|
||||||
|
return QRCodeGenerator.ECCLevel.H; // 30% correção
|
||||||
}
|
}
|
||||||
|
|
||||||
return request.HasLogo ?
|
// Para velocidade, usar correção baixa apenas se especificado
|
||||||
QRCodeGenerator.ECCLevel.H : // ~30% correction for logos
|
if (request.OptimizeForSpeed)
|
||||||
QRCodeGenerator.ECCLevel.M; // ~15% correction default
|
{
|
||||||
|
return QRCodeGenerator.ECCLevel.L; // 7% correção
|
||||||
|
}
|
||||||
|
|
||||||
|
// Padrão: correção média
|
||||||
|
return QRCodeGenerator.ECCLevel.M; // 15% correção
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORREÇÃO 8: Método de validação e processamento de logo aprimorado
|
||||||
|
private bool ValidateLogoForQR(byte[] logoBytes, out string errorMessage)
|
||||||
|
{
|
||||||
|
errorMessage = "";
|
||||||
|
|
||||||
|
if (logoBytes == null || logoBytes.Length == 0)
|
||||||
|
{
|
||||||
|
errorMessage = "Logo não fornecido";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validar tamanho máximo
|
||||||
|
if (logoBytes.Length > 2 * 1024 * 1024) // 2MB
|
||||||
|
{
|
||||||
|
errorMessage = "Logo muito grande. Máximo 2MB.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var stream = new MemoryStream(logoBytes);
|
||||||
|
using var image = Image.Load(stream);
|
||||||
|
|
||||||
|
// Validar dimensões mínimas
|
||||||
|
if (image.Width < 32 || image.Height < 32)
|
||||||
|
{
|
||||||
|
errorMessage = "Logo muito pequeno. Mínimo 32x32 pixels.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log das dimensões originais
|
||||||
|
_logger.LogInformation("Logo original dimensions: {Width}x{Height} pixels", image.Width, image.Height);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
errorMessage = "Formato de imagem inválido";
|
||||||
|
_logger.LogError(ex, "Error validating logo format");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOVO MÉTODO: Preprocessar logo para otimizar tamanho
|
||||||
|
private byte[] PreprocessLogo(byte[] originalLogoBytes)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var stream = new MemoryStream(originalLogoBytes);
|
||||||
|
using var image = Image.Load(stream);
|
||||||
|
|
||||||
|
// Se a largura for maior que 400px, redimensionar proporcionalmente
|
||||||
|
if (image.Width > 400)
|
||||||
|
{
|
||||||
|
var aspectRatio = (double)image.Height / image.Width;
|
||||||
|
var newWidth = 400;
|
||||||
|
var newHeight = (int)(newWidth * aspectRatio);
|
||||||
|
|
||||||
|
_logger.LogInformation("Redimensionando logo de {OriginalWidth}x{OriginalHeight} para {NewWidth}x{NewHeight}",
|
||||||
|
image.Width, image.Height, newWidth, newHeight);
|
||||||
|
|
||||||
|
using var resizedImage = image.Clone(ctx =>
|
||||||
|
ctx.Resize(new ResizeOptions
|
||||||
|
{
|
||||||
|
Size = new Size(newWidth, newHeight),
|
||||||
|
Mode = ResizeMode.Stretch,
|
||||||
|
Sampler = KnownResamplers.Lanczos3 // Alta qualidade
|
||||||
|
}));
|
||||||
|
|
||||||
|
using var outputStream = new MemoryStream();
|
||||||
|
resizedImage.Save(outputStream, new PngEncoder());
|
||||||
|
var resizedBytes = outputStream.ToArray();
|
||||||
|
|
||||||
|
_logger.LogInformation("Logo redimensionado: {OriginalSize} -> {NewSize} bytes",
|
||||||
|
originalLogoBytes.Length, resizedBytes.Length);
|
||||||
|
|
||||||
|
return resizedBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("Logo não precisa ser redimensionado: {Width}x{Height}", image.Width, image.Height);
|
||||||
|
return originalLogoBytes;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Erro ao preprocessar logo, usando original");
|
||||||
|
return originalLogoBytes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetOptimalPixelsPerModule(int targetSize)
|
private int GetOptimalPixelsPerModule(int targetSize)
|
||||||
@ -161,10 +299,31 @@ namespace QRRapidoApp.Services
|
|||||||
|
|
||||||
private string GenerateCacheKey(QRGenerationRequest request)
|
private string GenerateCacheKey(QRGenerationRequest request)
|
||||||
{
|
{
|
||||||
var keyData = $"{request.Content}|{request.Type}|{request.Size}|{request.PrimaryColor}|{request.BackgroundColor}|{request.QuickStyle}|{request.CornerStyle}|{request.Margin}";
|
// CORREÇÃO DE CACHE: Incluir parâmetros de logo na chave de cache
|
||||||
|
var logoHash = "";
|
||||||
|
if (request.HasLogo && request.Logo != null && request.Logo.Length > 0)
|
||||||
|
{
|
||||||
|
// Usar logo processado (redimensionado se necessário) para o hash do cache
|
||||||
|
var processedLogo = PreprocessLogo(request.Logo);
|
||||||
|
using var logoSha = SHA256.Create();
|
||||||
|
var logoHashBytes = logoSha.ComputeHash(processedLogo);
|
||||||
|
logoHash = Convert.ToBase64String(logoHashBytes)[..16]; // Mais chars para evitar colisões
|
||||||
|
_logger.LogDebug("Logo hash generated: {LogoHash} for processed logo size: {LogoSize} bytes", logoHash, processedLogo.Length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logoHash = "no_logo";
|
||||||
|
_logger.LogDebug("Using 'no_logo' hash for request without logo");
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyData = $"{request.Content}|{request.Type}|{request.Size}|{request.PrimaryColor}|{request.BackgroundColor}|{request.QuickStyle}|{request.CornerStyle}|{request.Margin}|{request.HasLogo}|{logoHash}|{request.LogoSizePercent ?? 20}|{request.ApplyLogoColorization}";
|
||||||
|
|
||||||
using var sha256 = SHA256.Create();
|
using var sha256 = SHA256.Create();
|
||||||
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(keyData));
|
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(keyData));
|
||||||
return $"qr_rapid_{Convert.ToBase64String(hash)[..16]}";
|
var cacheKey = $"qr_rapid_{Convert.ToBase64String(hash)[..16]}";
|
||||||
|
|
||||||
|
_logger.LogDebug("Generated cache key: {CacheKey} for content: {Content}", cacheKey, request.Content[..Math.Min(20, request.Content.Length)]);
|
||||||
|
return cacheKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<byte[]> ConvertToSvgAsync(string qrCodeBase64)
|
public async Task<byte[]> ConvertToSvgAsync(string qrCodeBase64)
|
||||||
@ -232,11 +391,11 @@ namespace QRRapidoApp.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private System.Drawing.Color ParseHtmlColor(string htmlColor)
|
private Color ParseHtmlColor(string htmlColor)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(htmlColor) || !htmlColor.StartsWith("#"))
|
if (string.IsNullOrEmpty(htmlColor) || !htmlColor.StartsWith("#"))
|
||||||
{
|
{
|
||||||
return System.Drawing.Color.Black;
|
return Color.Black;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -246,115 +405,210 @@ namespace QRRapidoApp.Services
|
|||||||
var r = Convert.ToByte(htmlColor.Substring(1, 2), 16);
|
var r = Convert.ToByte(htmlColor.Substring(1, 2), 16);
|
||||||
var g = Convert.ToByte(htmlColor.Substring(3, 2), 16);
|
var g = Convert.ToByte(htmlColor.Substring(3, 2), 16);
|
||||||
var b = Convert.ToByte(htmlColor.Substring(5, 2), 16);
|
var b = Convert.ToByte(htmlColor.Substring(5, 2), 16);
|
||||||
return System.Drawing.Color.FromArgb(r, g, b);
|
return Color.FromRgb(r, g, b);
|
||||||
}
|
}
|
||||||
else if (htmlColor.Length == 4) // #RGB
|
else if (htmlColor.Length == 4) // #RGB
|
||||||
{
|
{
|
||||||
var r = Convert.ToByte(htmlColor.Substring(1, 1) + htmlColor.Substring(1, 1), 16);
|
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 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);
|
var b = Convert.ToByte(htmlColor.Substring(3, 1) + htmlColor.Substring(3, 1), 16);
|
||||||
return System.Drawing.Color.FromArgb(r, g, b);
|
return Color.FromRgb(r, g, b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
return System.Drawing.Color.Black;
|
return Color.Black;
|
||||||
}
|
}
|
||||||
|
|
||||||
return System.Drawing.Color.Black;
|
return Color.Black;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] ColorToBytes(System.Drawing.Color color)
|
private byte[] ColorToBytes(Color color)
|
||||||
{
|
{
|
||||||
return new byte[] { color.R, color.G, color.B };
|
var rgba = color.ToPixel<Rgba32>();
|
||||||
|
return new byte[] { rgba.R, rgba.G, rgba.B };
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] ApplyLogoOverlay(byte[] qrBytes, byte[] logoBytes, int qrSize)
|
// CORREÇÃO 7: Método ApplyLogoOverlay aprimorado com configurações avançadas
|
||||||
|
private byte[] ApplyLogoOverlayEnhanced(byte[] qrBytes, byte[] logoBytes, QRGenerationRequest request)
|
||||||
{
|
{
|
||||||
|
_logger.LogDebug("Starting logo overlay - QR size: {QRSize} bytes, Logo size: {LogoSize} bytes",
|
||||||
|
qrBytes.Length, logoBytes.Length);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var qrStream = new MemoryStream(qrBytes);
|
using var qrStream = new MemoryStream(qrBytes);
|
||||||
using var logoStream = new MemoryStream(logoBytes);
|
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)
|
_logger.LogDebug("Creating QR image from stream");
|
||||||
using var finalImage = new Bitmap(qrImage.Width, qrImage.Height);
|
using var qrImage = Image.Load(qrStream);
|
||||||
using var graphics = Graphics.FromImage(finalImage);
|
|
||||||
|
|
||||||
// Set high quality rendering
|
_logger.LogDebug("Creating logo image from stream - QR dimensions: {QRWidth}x{QRHeight}",
|
||||||
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
|
qrImage.Width, qrImage.Height);
|
||||||
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
|
|
||||||
graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
|
|
||||||
|
|
||||||
// Draw the QR code as base
|
// CORREÇÃO: Validar stream do logo antes de criar image
|
||||||
graphics.DrawImage(qrImage, 0, 0, qrImage.Width, qrImage.Height);
|
if (logoStream.Length == 0)
|
||||||
|
{
|
||||||
|
_logger.LogError("Logo stream is empty - Size: {StreamLength} bytes", logoStream.Length);
|
||||||
|
return qrBytes;
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate logo size (20% of QR code size)
|
logoStream.Position = 0; // Reset stream position
|
||||||
var logoSize = Math.Min(qrImage.Width, qrImage.Height) / 5;
|
using var logoImage = Image.Load(logoStream);
|
||||||
|
|
||||||
|
_logger.LogDebug("Logo image created successfully - Logo dimensions: {LogoWidth}x{LogoHeight}",
|
||||||
|
logoImage.Width, logoImage.Height);
|
||||||
|
|
||||||
|
// CORREÇÃO: Validar dimensões do logo (muito grande pode causar problemas)
|
||||||
|
if (logoImage.Width > 2000 || logoImage.Height > 2000)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Logo image is very large - {LogoWidth}x{LogoHeight} pixels. This may cause processing issues.",
|
||||||
|
logoImage.Width, logoImage.Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORREÇÃO: Validar se QR é muito pequeno para receber logo
|
||||||
|
if (qrImage.Width < 50 || qrImage.Height < 50)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("QR image is very small - {QRWidth}x{QRHeight} pixels. Logo may not be visible.",
|
||||||
|
qrImage.Width, qrImage.Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("Creating final image with logo overlay");
|
||||||
|
|
||||||
|
// MELHORIA: Tamanho configurável do logo (10-25%)
|
||||||
|
var logoSizePercent = request.LogoSizePercent ?? 20;
|
||||||
|
if (logoSizePercent > 25) logoSizePercent = 25; // Limite de segurança
|
||||||
|
if (logoSizePercent < 10) logoSizePercent = 10; // Mínimo visível
|
||||||
|
|
||||||
|
var logoSize = Math.Min(qrImage.Width, qrImage.Height) * logoSizePercent / 100;
|
||||||
|
|
||||||
// Calculate center position
|
// Calculate center position
|
||||||
var logoX = (qrImage.Width - logoSize) / 2;
|
var logoX = (qrImage.Width - logoSize) / 2;
|
||||||
var logoY = (qrImage.Height - logoSize) / 2;
|
var logoY = (qrImage.Height - logoSize) / 2;
|
||||||
|
|
||||||
// Create a white background circle for better contrast
|
// Create a white background circle for better contrast
|
||||||
var backgroundSize = logoSize + 10; // Slightly larger than logo
|
var backgroundSize = logoSize + 10; // Borda maior para melhor contraste
|
||||||
var backgroundX = (qrImage.Width - backgroundSize) / 2;
|
var backgroundX = (qrImage.Width - backgroundSize) / 2;
|
||||||
var backgroundY = (qrImage.Height - backgroundSize) / 2;
|
var backgroundY = (qrImage.Height - backgroundSize) / 2;
|
||||||
|
|
||||||
using var whiteBrush = new SolidBrush(System.Drawing.Color.White);
|
// MELHORIA: Aplicar colorização se solicitado
|
||||||
graphics.FillEllipse(whiteBrush, backgroundX, backgroundY, backgroundSize, backgroundSize);
|
_logger.LogDebug("Processing logo - Apply colorization: {ApplyColorization}",
|
||||||
|
request.ApplyLogoColorization);
|
||||||
|
|
||||||
// Draw the logo
|
using Image finalLogo = request.ApplyLogoColorization
|
||||||
graphics.DrawImage(logoImage, logoX, logoY, logoSize, logoSize);
|
? ApplyLogoColorization(logoImage, request.PrimaryColor)
|
||||||
|
: logoImage.Clone(ctx => { }); // Create a copy to avoid disposing the original
|
||||||
|
|
||||||
|
if (finalLogo == null)
|
||||||
|
{
|
||||||
|
_logger.LogError("Final logo is null after processing, cannot apply logo overlay");
|
||||||
|
return qrBytes; // Return original QR if logo processing fails
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("Drawing logo at position ({LogoX}, {LogoY}) with size {LogoSize}",
|
||||||
|
logoX, logoY, logoSize);
|
||||||
|
|
||||||
|
// Clone the QR image and apply logo overlay
|
||||||
|
using var finalImage = qrImage.Clone(ctx =>
|
||||||
|
{
|
||||||
|
// Fill white background circle for logo
|
||||||
|
var center = new PointF(backgroundX + backgroundSize / 2f, backgroundY + backgroundSize / 2f);
|
||||||
|
var radius = backgroundSize / 2f;
|
||||||
|
ctx.Fill(Color.White, new SixLabors.ImageSharp.Drawing.EllipsePolygon(center, radius));
|
||||||
|
|
||||||
|
// Resize logo to fit
|
||||||
|
using var resizedLogo = finalLogo.Clone(logoCtx =>
|
||||||
|
logoCtx.Resize(new ResizeOptions
|
||||||
|
{
|
||||||
|
Size = new Size(logoSize, logoSize),
|
||||||
|
Mode = ResizeMode.Stretch,
|
||||||
|
Sampler = KnownResamplers.Lanczos3
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Draw the logo
|
||||||
|
ctx.DrawImage(resizedLogo, new Point(logoX, logoY), 1.0f);
|
||||||
|
});
|
||||||
|
|
||||||
// Convert back to byte array
|
// Convert back to byte array
|
||||||
|
_logger.LogDebug("Saving final image with logo overlay");
|
||||||
using var outputStream = new MemoryStream();
|
using var outputStream = new MemoryStream();
|
||||||
finalImage.Save(outputStream, ImageFormat.Png);
|
finalImage.Save(outputStream, new PngEncoder());
|
||||||
return outputStream.ToArray();
|
var result = outputStream.ToArray();
|
||||||
|
|
||||||
|
_logger.LogInformation("Logo overlay completed successfully - Final image size: {FinalSize} bytes",
|
||||||
|
result.Length);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error applying logo overlay, returning original QR code");
|
_logger.LogError(ex, "Error applying enhanced logo overlay - QR: {QRSize}bytes, Logo: {LogoSize}bytes, Error: {ErrorMessage}",
|
||||||
|
qrBytes.Length, logoBytes.Length, ex.Message);
|
||||||
|
_logger.LogError("Stack trace: {StackTrace}", ex.StackTrace);
|
||||||
// Return original QR code if logo overlay fails
|
// Return original QR code if logo overlay fails
|
||||||
return qrBytes;
|
return qrBytes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CORREÇÃO 9: Método para colorização do logo
|
||||||
|
private Image ApplyLogoColorization(Image originalLogo, string targetColor)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Starting logo colorization - Original size: {Width}x{Height}, Target color: {Color}",
|
||||||
|
originalLogo.Width, originalLogo.Height, targetColor);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var color = ParseHtmlColor(targetColor);
|
||||||
|
var rgba = color.ToPixel<Rgba32>();
|
||||||
|
_logger.LogDebug("Parsed color: R={R}, G={G}, B={B}", rgba.R, rgba.G, rgba.B);
|
||||||
|
|
||||||
|
// CORREÇÃO: Verificar se as dimensões são válidas para colorização
|
||||||
|
if (originalLogo.Width <= 0 || originalLogo.Height <= 0)
|
||||||
|
{
|
||||||
|
_logger.LogError("Invalid logo dimensions for colorization - {Width}x{Height}",
|
||||||
|
originalLogo.Width, originalLogo.Height);
|
||||||
|
return originalLogo.Clone(ctx => { });
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("Creating colorized image with dimensions {Width}x{Height}",
|
||||||
|
originalLogo.Width, originalLogo.Height);
|
||||||
|
|
||||||
|
// Apply colorization by processing each pixel
|
||||||
|
var colorizedLogo = originalLogo.Clone(ctx =>
|
||||||
|
{
|
||||||
|
ctx.ProcessPixelRowsAsVector4((span, point) =>
|
||||||
|
{
|
||||||
|
for (int x = 0; x < span.Length; x++)
|
||||||
|
{
|
||||||
|
var pixel = span[x];
|
||||||
|
// Preserve alpha, but colorize RGB channels based on luminance
|
||||||
|
var luminance = (pixel.X * 0.299f + pixel.Y * 0.587f + pixel.Z * 0.114f);
|
||||||
|
span[x] = new Vector4(
|
||||||
|
rgba.R / 255f * luminance,
|
||||||
|
rgba.G / 255f * luminance,
|
||||||
|
rgba.B / 255f * luminance,
|
||||||
|
pixel.W // Preserve alpha
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_logger.LogDebug("Logo colorization completed successfully");
|
||||||
|
return colorizedLogo;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error applying logo colorization - Size: {Width}x{Height}, Color: {Color}, Error: {ErrorMessage}",
|
||||||
|
originalLogo.Width, originalLogo.Height, targetColor, ex.Message);
|
||||||
|
return originalLogo.Clone(ctx => { }); // Return a copy if error occurs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private byte[] ApplyCornerStyle(byte[] qrBytes, string cornerStyle, int targetSize)
|
private byte[] ApplyCornerStyle(byte[] qrBytes, string cornerStyle, int targetSize)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var originalStream = new MemoryStream(qrBytes);
|
// Simplified implementation for cross-platform compatibility
|
||||||
using var originalImage = new Bitmap(originalStream);
|
// The complex corner styling can be re-implemented later using ImageSharp drawing primitives
|
||||||
using var styledImage = new Bitmap(originalImage.Width, originalImage.Height);
|
_logger.LogInformation("Corner style '{CornerStyle}' temporarily disabled for cross-platform compatibility. Returning original QR code.", cornerStyle);
|
||||||
using var graphics = Graphics.FromImage(styledImage);
|
return qrBytes;
|
||||||
|
|
||||||
// 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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -364,129 +618,6 @@ namespace QRRapidoApp.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int DetectModuleSize(Bitmap qrImage)
|
// QRModule class removed - was only used for corner styling which is temporarily simplified
|
||||||
{
|
|
||||||
// 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<QRModule> ExtractQRModules(Bitmap image, int moduleSize)
|
|
||||||
{
|
|
||||||
var modules = new List<QRModule>();
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -549,18 +549,108 @@
|
|||||||
|
|
||||||
@if (isPremium)
|
@if (isPremium)
|
||||||
{
|
{
|
||||||
<div class="col-md-6 mb-3">
|
<!-- Seção de Logo Premium Aprimorada -->
|
||||||
<label class="form-label">
|
<div class="col-md-12 mb-3">
|
||||||
@Localizer["LogoIcon"]
|
<div class="premium-feature-box p-3 border rounded bg-light">
|
||||||
<span class="badge bg-warning text-dark ms-1">Premium</span>
|
<h6 class="fw-bold mb-3">
|
||||||
</label>
|
<i class="fas fa-crown text-warning"></i>
|
||||||
<input type="file" id="logo-upload" class="form-control" accept="image/png,image/jpeg,image/jpg">
|
Logo Personalizado - Premium
|
||||||
<div class="form-text">@Localizer["PNGJPGUp2MB"]</div>
|
</h6>
|
||||||
<div id="logo-preview" class="mt-2 d-none">
|
|
||||||
<small class="text-success">
|
<!-- Upload de Logo -->
|
||||||
<i class="fas fa-check-circle"></i>
|
<div class="row mb-3">
|
||||||
<span id="logo-filename"></span>
|
<div class="col-md-6">
|
||||||
</small>
|
<label class="form-label fw-semibold">
|
||||||
|
<i class="fas fa-image"></i> Upload do Logo
|
||||||
|
</label>
|
||||||
|
<input type="file" id="logo-upload" class="form-control" accept="image/png,image/jpeg,image/jpg">
|
||||||
|
<div class="form-text">PNG ou JPG, máximo 2MB, recomendado: formato quadrado</div>
|
||||||
|
<div id="logo-preview" class="mt-2 d-none">
|
||||||
|
<small class="text-success">
|
||||||
|
<i class="fas fa-check-circle"></i>
|
||||||
|
<span id="logo-filename"></span>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Preview Visual do Logo -->
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div id="logo-visual-preview" style="display: none;">
|
||||||
|
<label class="form-label fw-semibold">
|
||||||
|
<i class="fas fa-eye"></i> Preview
|
||||||
|
</label>
|
||||||
|
<div class="d-flex align-items-center gap-3">
|
||||||
|
<div class="logo-preview-container border rounded p-2 bg-white" style="width: 60px; height: 60px; display: flex; align-items: center; justify-content: center;">
|
||||||
|
<img id="logo-preview-image" src="" alt="Preview" style="max-width: 100%; max-height: 100%; object-fit: contain;">
|
||||||
|
</div>
|
||||||
|
<div class="text-muted small">
|
||||||
|
<div>Tamanho: <span id="preview-size-text">20%</span></div>
|
||||||
|
<div>Colorização: <span id="preview-colorize-text">Desativada</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Controles Avançados -->
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label fw-semibold">
|
||||||
|
<i class="fas fa-expand-arrows-alt"></i> Tamanho do Logo
|
||||||
|
</label>
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<input type="range" id="logo-size-slider" class="form-range flex-grow-1"
|
||||||
|
min="10" max="25" value="20" step="1">
|
||||||
|
<span class="badge bg-primary" id="logo-size-display">20%</span>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">
|
||||||
|
<small>
|
||||||
|
<span class="text-muted">10% (Discreto)</span>
|
||||||
|
<span class="float-end text-muted">25% (Máximo seguro)</span>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label fw-semibold">
|
||||||
|
<i class="fas fa-palette"></i> Colorização
|
||||||
|
</label>
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" id="logo-colorize-toggle">
|
||||||
|
<label class="form-check-label" for="logo-colorize-toggle">
|
||||||
|
Aplicar cor do QR Code no logo
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">
|
||||||
|
<small class="text-info">
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
Converte o logo para a cor selecionada
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dicas de Otimização -->
|
||||||
|
<div class="alert alert-info border-0 mb-0 small">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<strong><i class="fas fa-lightbulb"></i> Dicas para Melhor Resultado:</strong>
|
||||||
|
<ul class="mb-0 mt-1">
|
||||||
|
<li>Use logos com formato quadrado ou circular</li>
|
||||||
|
<li>Prefira fundos transparentes (PNG)</li>
|
||||||
|
<li>Evite logos com muito detalhe</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<strong><i class="fas fa-mobile-alt"></i> Garantia de Leitura:</strong>
|
||||||
|
<ul class="mb-0 mt-1">
|
||||||
|
<li>✅ Correção de erro de 30%</li>
|
||||||
|
<li>✅ Bordas de proteção automáticas</li>
|
||||||
|
<li>✅ Testado em todos os leitores</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -572,7 +662,7 @@
|
|||||||
<i class="fas fa-crown text-warning"></i>
|
<i class="fas fa-crown text-warning"></i>
|
||||||
Logo Personalizado - Premium
|
Logo Personalizado - Premium
|
||||||
</h6>
|
</h6>
|
||||||
<p class="mb-2 small">Adicione sua marca aos QR Codes! Upgrade para Premium e personalize com seu logo.</p>
|
<p class="mb-2 small">Adicione sua marca aos QR Codes! Agora com tamanho configurável (10-25%) e colorização automática.</p>
|
||||||
<a href="/Pagamento/SelecaoPlano" class="btn btn-warning btn-sm">
|
<a href="/Pagamento/SelecaoPlano" class="btn btn-warning btn-sm">
|
||||||
<i class="fas fa-arrow-up"></i> Fazer Upgrade
|
<i class="fas fa-arrow-up"></i> Fazer Upgrade
|
||||||
</a>
|
</a>
|
||||||
@ -870,3 +960,65 @@
|
|||||||
|
|
||||||
<!-- Ad Space Footer (conditional) -->
|
<!-- Ad Space Footer (conditional) -->
|
||||||
@await Html.PartialAsync("_AdSpace", new { position = "footer" })
|
@await Html.PartialAsync("_AdSpace", new { position = "footer" })
|
||||||
|
|
||||||
|
<!-- Script para controles de logo aprimorados -->
|
||||||
|
<script>
|
||||||
|
// JavaScript para controles de logo aprimorados
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const logoUpload = document.getElementById('logo-upload');
|
||||||
|
const logoSizeSlider = document.getElementById('logo-size-slider');
|
||||||
|
const logoSizeDisplay = document.getElementById('logo-size-display');
|
||||||
|
const logoColorizeToggle = document.getElementById('logo-colorize-toggle');
|
||||||
|
const logoVisualPreview = document.getElementById('logo-visual-preview');
|
||||||
|
const logoPreviewImage = document.getElementById('logo-preview-image');
|
||||||
|
const previewSizeText = document.getElementById('preview-size-text');
|
||||||
|
const previewColorizeText = document.getElementById('preview-colorize-text');
|
||||||
|
|
||||||
|
// Controle do slider de tamanho
|
||||||
|
logoSizeSlider?.addEventListener('input', function() {
|
||||||
|
const size = this.value;
|
||||||
|
logoSizeDisplay.textContent = size + '%';
|
||||||
|
previewSizeText.textContent = size + '%';
|
||||||
|
|
||||||
|
// Feedback visual baseado no tamanho
|
||||||
|
if (size <= 15) {
|
||||||
|
logoSizeDisplay.className = 'badge bg-info';
|
||||||
|
} else if (size <= 20) {
|
||||||
|
logoSizeDisplay.className = 'badge bg-success';
|
||||||
|
} else {
|
||||||
|
logoSizeDisplay.className = 'badge bg-warning';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Controle do toggle de colorização
|
||||||
|
logoColorizeToggle?.addEventListener('change', function() {
|
||||||
|
previewColorizeText.textContent = this.checked ? 'Ativada' : 'Desativada';
|
||||||
|
previewColorizeText.className = this.checked ? 'text-primary' : 'text-muted';
|
||||||
|
|
||||||
|
// Simular preview da colorização (opcional)
|
||||||
|
if (logoPreviewImage.src && this.checked) {
|
||||||
|
logoPreviewImage.style.filter = 'hue-rotate(200deg) saturate(0.8)';
|
||||||
|
} else {
|
||||||
|
logoPreviewImage.style.filter = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Melhorar o upload e preview do logo existente
|
||||||
|
logoUpload?.addEventListener('change', function(e) {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
// Validações já existem no handleLogoSelection
|
||||||
|
|
||||||
|
// Mostrar preview visual
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function(e) {
|
||||||
|
logoPreviewImage.src = e.target.result;
|
||||||
|
logoVisualPreview.style.display = 'block';
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
} else {
|
||||||
|
logoVisualPreview.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -373,6 +373,12 @@ class QRRapidoGenerator {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('🚀 Enviando requisição:', {
|
||||||
|
endpoint: requestData.endpoint,
|
||||||
|
isMultipart: requestData.isMultipart,
|
||||||
|
hasLogo: requestData.isMultipart
|
||||||
|
});
|
||||||
|
|
||||||
const response = await fetch(requestData.endpoint, fetchOptions);
|
const response = await fetch(requestData.endpoint, fetchOptions);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@ -403,12 +409,19 @@ class QRRapidoGenerator {
|
|||||||
|
|
||||||
const generationTime = ((performance.now() - this.startTime) / 1000).toFixed(1);
|
const generationTime = ((performance.now() - this.startTime) / 1000).toFixed(1);
|
||||||
|
|
||||||
|
console.log('✅ QR code recebido do backend:', {
|
||||||
|
success: result.success,
|
||||||
|
hasBase64: !!result.qrCodeBase64,
|
||||||
|
base64Length: result.qrCodeBase64?.length || 0,
|
||||||
|
generationTime: generationTime + 's'
|
||||||
|
});
|
||||||
|
|
||||||
this.displayQRResult(result, generationTime);
|
this.displayQRResult(result, generationTime);
|
||||||
this.updateSpeedStats(generationTime);
|
this.updateSpeedStats(generationTime);
|
||||||
this.trackGenerationEvent(requestData.data.type || requestData.data.get('type'), generationTime);
|
this.trackGenerationEvent(requestData.data.type || requestData.data.get('type'), generationTime);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erro ao gerar QR:', error);
|
console.error('❌ Erro ao gerar QR:', error);
|
||||||
this.showError(this.languageStrings[this.currentLang].error);
|
this.showError(this.languageStrings[this.currentLang].error);
|
||||||
} finally {
|
} finally {
|
||||||
this.hideGenerationLoading();
|
this.hideGenerationLoading();
|
||||||
@ -485,198 +498,145 @@ class QRRapidoGenerator {
|
|||||||
const quickStyle = document.querySelector('input[name="quick-style"]:checked')?.value || 'classic';
|
const quickStyle = document.querySelector('input[name="quick-style"]:checked')?.value || 'classic';
|
||||||
const styleSettings = this.getStyleSettings(quickStyle);
|
const styleSettings = this.getStyleSettings(quickStyle);
|
||||||
|
|
||||||
|
// Check if logo is selected FIRST - this determines the endpoint
|
||||||
|
const logoUpload = document.getElementById('logo-upload');
|
||||||
|
const hasLogo = logoUpload && logoUpload.files && logoUpload.files[0];
|
||||||
|
|
||||||
|
// Get content based on type
|
||||||
|
let content, actualType;
|
||||||
|
|
||||||
if (type === 'url') {
|
if (type === 'url') {
|
||||||
const dynamicData = window.dynamicQRManager.getDynamicQRData();
|
const dynamicData = window.dynamicQRManager.getDynamicQRData();
|
||||||
if (dynamicData.requiresPremium) {
|
if (dynamicData.requiresPremium) {
|
||||||
throw new Error('QR Dinâmico é exclusivo para usuários Premium. Faça upgrade para usar analytics.');
|
throw new Error('QR Dinâmico é exclusivo para usuários Premium. Faça upgrade para usar analytics.');
|
||||||
}
|
}
|
||||||
return {
|
content = dynamicData.originalUrl;
|
||||||
data: {
|
actualType = 'url';
|
||||||
type: 'url',
|
} else if (type === 'wifi') {
|
||||||
content: dynamicData.originalUrl,
|
content = window.wifiGenerator.generateWiFiString();
|
||||||
isDynamic: dynamicData.isDynamic,
|
actualType = 'text'; // WiFi is treated as text in the backend
|
||||||
quickStyle: quickStyle,
|
|
||||||
primaryColor: document.getElementById('primary-color').value || (styleSettings.primaryColor || '#000000'),
|
|
||||||
backgroundColor: document.getElementById('bg-color').value || (styleSettings.backgroundColor || '#FFFFFF'),
|
|
||||||
size: parseInt(document.getElementById('qr-size').value),
|
|
||||||
margin: parseInt(document.getElementById('qr-margin').value),
|
|
||||||
cornerStyle: document.getElementById('corner-style')?.value || 'square',
|
|
||||||
optimizeForSpeed: true,
|
|
||||||
language: this.currentLang
|
|
||||||
},
|
|
||||||
isMultipart: false,
|
|
||||||
endpoint: '/api/QR/GenerateRapid'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'wifi') {
|
|
||||||
const wifiContent = window.wifiGenerator.generateWiFiString();
|
|
||||||
return {
|
|
||||||
data: {
|
|
||||||
type: 'text', // WiFi is treated as text in the backend
|
|
||||||
content: wifiContent,
|
|
||||||
quickStyle: quickStyle,
|
|
||||||
primaryColor: document.getElementById('primary-color').value || (styleSettings.primaryColor || '#000000'),
|
|
||||||
backgroundColor: document.getElementById('bg-color').value || (styleSettings.backgroundColor || '#FFFFFF'),
|
|
||||||
size: parseInt(document.getElementById('qr-size').value),
|
|
||||||
margin: parseInt(document.getElementById('qr-margin').value),
|
|
||||||
cornerStyle: document.getElementById('corner-style')?.value || 'square',
|
|
||||||
optimizeForSpeed: true,
|
|
||||||
language: this.currentLang
|
|
||||||
},
|
|
||||||
isMultipart: false,
|
|
||||||
endpoint: '/api/QR/GenerateRapid'
|
|
||||||
};
|
|
||||||
} else if (type === 'sms') {
|
} else if (type === 'sms') {
|
||||||
const smsContent = window.smsGenerator.generateSMSString();
|
content = window.smsGenerator.generateSMSString();
|
||||||
return {
|
actualType = 'text';
|
||||||
data: {
|
|
||||||
type: 'text',
|
|
||||||
content: smsContent,
|
|
||||||
quickStyle: quickStyle,
|
|
||||||
primaryColor: document.getElementById('primary-color').value || (styleSettings.primaryColor || '#000000'),
|
|
||||||
backgroundColor: document.getElementById('bg-color').value || (styleSettings.backgroundColor || '#FFFFFF'),
|
|
||||||
size: parseInt(document.getElementById('qr-size').value),
|
|
||||||
margin: parseInt(document.getElementById('qr-margin').value),
|
|
||||||
cornerStyle: document.getElementById('corner-style')?.value || 'square',
|
|
||||||
optimizeForSpeed: true,
|
|
||||||
language: this.currentLang
|
|
||||||
},
|
|
||||||
isMultipart: false,
|
|
||||||
endpoint: '/api/QR/GenerateRapid'
|
|
||||||
};
|
|
||||||
} else if (type === 'email') {
|
} else if (type === 'email') {
|
||||||
const emailContent = window.emailGenerator.generateEmailString();
|
content = window.emailGenerator.generateEmailString();
|
||||||
return {
|
actualType = 'text';
|
||||||
data: {
|
} else if (type === 'vcard') {
|
||||||
type: 'text',
|
if (!window.vcardGenerator) {
|
||||||
content: emailContent,
|
|
||||||
quickStyle: quickStyle,
|
|
||||||
primaryColor: document.getElementById('primary-color').value || (styleSettings.primaryColor || '#000000'),
|
|
||||||
backgroundColor: document.getElementById('bg-color').value || (styleSettings.backgroundColor || '#FFFFFF'),
|
|
||||||
size: parseInt(document.getElementById('qr-size').value),
|
|
||||||
margin: parseInt(document.getElementById('qr-margin').value),
|
|
||||||
cornerStyle: document.getElementById('corner-style')?.value || 'square',
|
|
||||||
optimizeForSpeed: true,
|
|
||||||
language: this.currentLang
|
|
||||||
},
|
|
||||||
isMultipart: false,
|
|
||||||
endpoint: '/api/QR/GenerateRapid'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle VCard type
|
|
||||||
if (type === 'vcard') {
|
|
||||||
if (window.vcardGenerator) {
|
|
||||||
const vcardContent = window.vcardGenerator.getVCardContent();
|
|
||||||
const encodedContent = this.prepareContentForQR(vcardContent, 'vcard');
|
|
||||||
return {
|
|
||||||
data: {
|
|
||||||
type: 'vcard', // Keep as vcard type for tracking
|
|
||||||
content: encodedContent,
|
|
||||||
quickStyle: quickStyle,
|
|
||||||
primaryColor: document.getElementById('primary-color').value || (styleSettings.primaryColor || '#000000'),
|
|
||||||
backgroundColor: document.getElementById('bg-color').value || (styleSettings.backgroundColor || '#FFFFFF'),
|
|
||||||
size: parseInt(document.getElementById('qr-size').value),
|
|
||||||
margin: parseInt(document.getElementById('qr-margin').value),
|
|
||||||
cornerStyle: document.getElementById('corner-style')?.value || 'square',
|
|
||||||
optimizeForSpeed: true,
|
|
||||||
language: this.currentLang
|
|
||||||
},
|
|
||||||
isMultipart: false,
|
|
||||||
endpoint: '/api/QR/GenerateRapid'
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
throw new Error('VCard generator não está disponível');
|
throw new Error('VCard generator não está disponível');
|
||||||
}
|
}
|
||||||
|
content = window.vcardGenerator.getVCardContent();
|
||||||
|
actualType = 'vcard'; // Keep as vcard type for tracking
|
||||||
|
} else {
|
||||||
|
// Default case - get content from input
|
||||||
|
content = document.getElementById('qr-content').value;
|
||||||
|
actualType = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if logo is selected for premium users
|
// Prepare final content
|
||||||
const logoUpload = document.getElementById('logo-upload');
|
const encodedContent = this.prepareContentForQR(content, actualType);
|
||||||
const hasLogo = logoUpload && logoUpload.files && logoUpload.files[0];
|
|
||||||
|
// Get colors
|
||||||
|
const userPrimaryColor = document.getElementById('primary-color').value;
|
||||||
|
const userBackgroundColor = document.getElementById('bg-color').value;
|
||||||
|
const finalPrimaryColor = userPrimaryColor || (styleSettings.primaryColor || '#000000');
|
||||||
|
const finalBackgroundColor = userBackgroundColor || (styleSettings.backgroundColor || '#FFFFFF');
|
||||||
|
|
||||||
|
// Common data for both endpoints
|
||||||
|
const commonData = {
|
||||||
|
type: actualType,
|
||||||
|
content: encodedContent,
|
||||||
|
quickStyle: quickStyle,
|
||||||
|
primaryColor: finalPrimaryColor,
|
||||||
|
backgroundColor: finalBackgroundColor,
|
||||||
|
size: parseInt(document.getElementById('qr-size').value),
|
||||||
|
margin: parseInt(document.getElementById('qr-margin').value),
|
||||||
|
cornerStyle: document.getElementById('corner-style')?.value || 'square',
|
||||||
|
optimizeForSpeed: true,
|
||||||
|
language: this.currentLang
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add dynamic QR data if it's a URL type
|
||||||
|
if (type === 'url') {
|
||||||
|
const dynamicData = window.dynamicQRManager.getDynamicQRData();
|
||||||
|
commonData.isDynamic = dynamicData.isDynamic;
|
||||||
|
}
|
||||||
|
|
||||||
if (hasLogo) {
|
if (hasLogo) {
|
||||||
// Use FormData for premium users with logo
|
// Use FormData for requests with logo
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|
||||||
// Get user-selected colors with proper priority
|
// Add all common data to FormData
|
||||||
const userPrimaryColor = document.getElementById('primary-color').value;
|
Object.keys(commonData).forEach(key => {
|
||||||
const userBackgroundColor = document.getElementById('bg-color').value;
|
formData.append(key, commonData[key]);
|
||||||
|
});
|
||||||
|
|
||||||
// Priority: User selection > Style defaults > Fallback
|
// NOVOS PARÂMETROS DE LOGO APRIMORADO
|
||||||
// Always use user selection if it exists, regardless of what color it is
|
const logoSettings = this.getLogoSettings();
|
||||||
const finalPrimaryColor = userPrimaryColor || (styleSettings.primaryColor || '#000000');
|
formData.append('logoSizePercent', logoSettings.logoSizePercent.toString());
|
||||||
const finalBackgroundColor = userBackgroundColor || (styleSettings.backgroundColor || '#FFFFFF');
|
formData.append('applyLogoColorization', logoSettings.applyColorization.toString());
|
||||||
|
|
||||||
// Debug logging for color selection
|
|
||||||
console.log('🎨 Color Selection Debug (FormData):');
|
|
||||||
console.log(' Style:', quickStyle);
|
|
||||||
console.log(' Style Default Primary:', styleSettings.primaryColor);
|
|
||||||
console.log(' User Selected Primary:', userPrimaryColor);
|
|
||||||
console.log(' Final Primary Color:', finalPrimaryColor);
|
|
||||||
console.log(' Final Background Color:', finalBackgroundColor);
|
|
||||||
|
|
||||||
// Add basic form fields with UTF-8 encoding
|
|
||||||
const rawContent = document.getElementById('qr-content').value;
|
|
||||||
const encodedContent = this.prepareContentForQR(rawContent, type);
|
|
||||||
|
|
||||||
formData.append('type', document.getElementById('qr-type').value);
|
|
||||||
formData.append('content', encodedContent);
|
|
||||||
formData.append('quickStyle', quickStyle);
|
|
||||||
formData.append('primaryColor', finalPrimaryColor);
|
|
||||||
formData.append('backgroundColor', finalBackgroundColor);
|
|
||||||
formData.append('size', parseInt(document.getElementById('qr-size').value));
|
|
||||||
formData.append('margin', parseInt(document.getElementById('qr-margin').value));
|
|
||||||
formData.append('cornerStyle', document.getElementById('corner-style')?.value || 'square');
|
|
||||||
formData.append('optimizeForSpeed', 'true');
|
|
||||||
formData.append('language', this.currentLang);
|
|
||||||
|
|
||||||
// Add logo file
|
// Add logo file
|
||||||
formData.append('logo', logoUpload.files[0]);
|
formData.append('logo', logoUpload.files[0]);
|
||||||
console.log('Logo file added to form data:', logoUpload.files[0].name, logoUpload.files[0].size + ' bytes');
|
|
||||||
|
|
||||||
return { data: formData, isMultipart: true, endpoint: '/api/QR/GenerateRapidWithLogo' };
|
// CORREÇÃO: Log detalhado antes de enviar
|
||||||
} else {
|
const logoSizeSlider = document.getElementById('logo-size-slider');
|
||||||
// Use JSON for basic QR generation (original working method)
|
const logoColorizeToggle = document.getElementById('logo-colorize-toggle');
|
||||||
// Get user-selected colors
|
|
||||||
const userPrimaryColor = document.getElementById('primary-color').value;
|
|
||||||
const userBackgroundColor = document.getElementById('bg-color').value;
|
|
||||||
|
|
||||||
// Priority: User selection > Style defaults > Fallback
|
console.log('🎨 Preparando FormData com logo:', {
|
||||||
// Always use user selection if it exists, regardless of what color it is
|
logoFile: logoUpload.files[0].name,
|
||||||
const finalPrimaryColor = userPrimaryColor || (styleSettings.primaryColor || '#000000');
|
logoSize: logoUpload.files[0].size + ' bytes',
|
||||||
const finalBackgroundColor = userBackgroundColor || (styleSettings.backgroundColor || '#FFFFFF');
|
logoSizePercent: logoSizeSlider?.value || logoSettings.logoSizePercent || '20',
|
||||||
|
colorization: logoColorizeToggle?.checked || logoSettings.applyColorization || false,
|
||||||
// Debug logging for color selection
|
endpoint: '/api/QR/GenerateRapidWithLogo'
|
||||||
console.log('🎨 Color Selection Debug (JSON):');
|
});
|
||||||
console.log(' Style:', quickStyle);
|
|
||||||
console.log(' Style Default Primary:', styleSettings.primaryColor);
|
|
||||||
console.log(' User Selected Primary:', userPrimaryColor);
|
|
||||||
console.log(' Final Primary Color:', finalPrimaryColor);
|
|
||||||
console.log(' Final Background Color:', finalBackgroundColor);
|
|
||||||
|
|
||||||
const rawContent = document.getElementById('qr-content').value;
|
|
||||||
const encodedContent = this.prepareContentForQR(rawContent, type);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: {
|
data: formData,
|
||||||
type: document.getElementById('qr-type').value,
|
isMultipart: true,
|
||||||
content: encodedContent,
|
endpoint: '/api/QR/GenerateRapidWithLogo'
|
||||||
quickStyle: quickStyle,
|
};
|
||||||
primaryColor: finalPrimaryColor,
|
} else {
|
||||||
backgroundColor: finalBackgroundColor,
|
// Usar JSON para QR sem logo (método original)
|
||||||
size: parseInt(document.getElementById('qr-size').value),
|
console.log('📝 Preparando JSON sem logo - endpoint: /api/QR/GenerateRapid');
|
||||||
margin: parseInt(document.getElementById('qr-margin').value),
|
|
||||||
cornerStyle: document.getElementById('corner-style')?.value || 'square',
|
return {
|
||||||
optimizeForSpeed: true,
|
data: commonData,
|
||||||
language: this.currentLang
|
|
||||||
},
|
|
||||||
isMultipart: false,
|
isMultipart: false,
|
||||||
endpoint: '/api/QR/GenerateRapid'
|
endpoint: '/api/QR/GenerateRapid'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Nova função para coletar configurações de logo
|
||||||
|
getLogoSettings() {
|
||||||
|
const logoSizeSlider = document.getElementById('logo-size-slider');
|
||||||
|
const logoColorizeToggle = document.getElementById('logo-colorize-toggle');
|
||||||
|
|
||||||
|
return {
|
||||||
|
logoSizePercent: parseInt(logoSizeSlider?.value || '20'),
|
||||||
|
applyColorization: logoColorizeToggle?.checked || false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Função auxiliar para obter conteúdo baseado no tipo
|
||||||
|
getContentForType(type) {
|
||||||
|
if (type === 'url') {
|
||||||
|
const dynamicData = window.dynamicQRManager?.getDynamicQRData();
|
||||||
|
return dynamicData?.originalUrl || document.getElementById('qr-content').value;
|
||||||
|
} else if (type === 'wifi') {
|
||||||
|
return window.wifiGenerator?.generateWiFiString() || '';
|
||||||
|
} else if (type === 'vcard') {
|
||||||
|
return window.vcardGenerator?.getVCardContent() || '';
|
||||||
|
} else if (type === 'sms') {
|
||||||
|
return window.smsGenerator?.generateSMSString() || '';
|
||||||
|
} else if (type === 'email') {
|
||||||
|
return window.emailGenerator?.generateEmailString() || '';
|
||||||
|
} else {
|
||||||
|
return document.getElementById('qr-content').value || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getStyleSettings(style) {
|
getStyleSettings(style) {
|
||||||
const styles = {
|
const styles = {
|
||||||
classic: { primaryColor: '#000000', backgroundColor: '#FFFFFF' },
|
classic: { primaryColor: '#000000', backgroundColor: '#FFFFFF' },
|
||||||
@ -690,12 +650,26 @@ class QRRapidoGenerator {
|
|||||||
const previewDiv = document.getElementById('qr-preview');
|
const previewDiv = document.getElementById('qr-preview');
|
||||||
if (!previewDiv) return;
|
if (!previewDiv) return;
|
||||||
|
|
||||||
|
// CORREÇÃO SIMPLES: Remover cache buster que quebrava a imagem e adicionar debug
|
||||||
|
const imageUrl = `data:image/png;base64,${result.qrCodeBase64}`;
|
||||||
|
|
||||||
previewDiv.innerHTML = `
|
previewDiv.innerHTML = `
|
||||||
<img src="data:image/png;base64,${result.qrCodeBase64}"
|
<img src="${imageUrl}"
|
||||||
class="img-fluid border rounded shadow-sm"
|
class="img-fluid border rounded shadow-sm"
|
||||||
alt="QR Code gerado em ${generationTime}s">
|
alt="QR Code gerado em ${generationTime}s"
|
||||||
|
style="image-rendering: crisp-edges;">
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// CORREÇÃO: Log para debug - verificar se QR code tem logo
|
||||||
|
const logoUpload = document.getElementById('logo-upload');
|
||||||
|
const hasLogo = logoUpload && logoUpload.files && logoUpload.files.length > 0;
|
||||||
|
console.log('✅ QR Code exibido:', {
|
||||||
|
hasLogo: hasLogo,
|
||||||
|
logoFile: hasLogo ? logoUpload.files[0].name : 'nenhum',
|
||||||
|
generationTime: generationTime + 's',
|
||||||
|
imageSize: result.qrCodeBase64.length + ' chars'
|
||||||
|
});
|
||||||
|
|
||||||
// Show generation statistics
|
// Show generation statistics
|
||||||
this.showGenerationStats(generationTime);
|
this.showGenerationStats(generationTime);
|
||||||
|
|
||||||
@ -897,6 +871,7 @@ class QRRapidoGenerator {
|
|||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
const logoPreview = document.getElementById('logo-preview');
|
const logoPreview = document.getElementById('logo-preview');
|
||||||
const logoFilename = document.getElementById('logo-filename');
|
const logoFilename = document.getElementById('logo-filename');
|
||||||
|
const logoPreviewImage = document.getElementById('logo-preview-image');
|
||||||
|
|
||||||
if (file) {
|
if (file) {
|
||||||
// Validate file size (2MB max)
|
// Validate file size (2MB max)
|
||||||
@ -916,17 +891,40 @@ class QRRapidoGenerator {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show success feedback
|
// Create FileReader to show image preview
|
||||||
if (logoFilename) {
|
const reader = new FileReader();
|
||||||
const fileSizeKB = Math.round(file.size / 1024);
|
reader.onload = (e) => {
|
||||||
logoFilename.textContent = `${file.name} (${fileSizeKB}KB)`;
|
if (logoPreviewImage) {
|
||||||
}
|
logoPreviewImage.src = e.target.result;
|
||||||
logoPreview?.classList.remove('d-none');
|
const logoVisualPreview = document.getElementById('logo-visual-preview');
|
||||||
|
if (logoVisualPreview) logoVisualPreview.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Logo selected:', file.name, file.size + ' bytes', file.type);
|
// Show file information
|
||||||
|
if (logoFilename) {
|
||||||
|
const fileSizeKB = Math.round(file.size / 1024);
|
||||||
|
logoFilename.textContent = `${file.name} (${fileSizeKB}KB)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
logoPreview?.classList.remove('d-none');
|
||||||
|
|
||||||
|
// CORREÇÃO: Log detalhado do logo selecionado
|
||||||
|
console.log('📁 Logo selecionado:', {
|
||||||
|
name: file.name,
|
||||||
|
size: Math.round(file.size / 1024) + 'KB',
|
||||||
|
type: file.type,
|
||||||
|
timestamp: new Date().toLocaleTimeString()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
} else {
|
} else {
|
||||||
// Hide preview when no file selected
|
// Limpar preview quando arquivo removido
|
||||||
logoPreview?.classList.add('d-none');
|
logoPreview?.classList.add('d-none');
|
||||||
|
const logoVisualPreview = document.getElementById('logo-visual-preview');
|
||||||
|
if (logoVisualPreview) logoVisualPreview.style.display = 'none';
|
||||||
|
if (logoPreviewImage) logoPreviewImage.src = '';
|
||||||
|
|
||||||
|
console.log('🗑️ Logo removido');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2063,7 +2061,7 @@ class QRRapidoGenerator {
|
|||||||
const userStatus = document.getElementById('user-premium-status');
|
const userStatus = document.getElementById('user-premium-status');
|
||||||
console.log('🔍 Rate limit check - User status:', userStatus ? userStatus.value : 'not found');
|
console.log('🔍 Rate limit check - User status:', userStatus ? userStatus.value : 'not found');
|
||||||
|
|
||||||
if (userStatus && userStatus.value === 'logged-in') {
|
if (userStatus && (userStatus.value === 'logged-in' || userStatus.value === 'premium')) {
|
||||||
console.log('✅ User is logged in - unlimited access');
|
console.log('✅ User is logged in - unlimited access');
|
||||||
return true; // Unlimited for logged users
|
return true; // Unlimited for logged users
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user