diff --git a/src/app/(authenticated)/image-editor-demo/page.tsx b/src/app/(authenticated)/image-editor-demo/page.tsx deleted file mode 100644 index aa9ffead..00000000 --- a/src/app/(authenticated)/image-editor-demo/page.tsx +++ /dev/null @@ -1,205 +0,0 @@ -"use client" - -import { useState } from "react" -import { DashboardShell } from "@/components/dashboard-shell" -import { ImageEditor } from "@/components/image-editor" -import { Button } from "@/components/ui/button" -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" -import { ImageIcon, Upload } from "lucide-react" -import Image from "next/image" - -export default function ImageEditorDemoPage() { - const [imagemEditada, setImagemEditada] = useState(null) - const [imagemOriginal, setImagemOriginal] = useState(null) - - const handleFileUpload = (event: React.ChangeEvent) => { - const file = event.target.files?.[0] - if (file?.type.startsWith('image/')) { - const url = URL.createObjectURL(file) - setImagemOriginal(url) - setImagemEditada(url) - } - } - - return ( - -
-
-

Editor de Imagem - Demo

-

- Teste as funcionalidades de edição de imagem: redimensionamento, recorte, blur e adição de figurinhas -

-
- -
- {/* Upload de Imagem */} - - - 1. Carregar Imagem - - - - - - {imagemOriginal && ( -
-

Imagem Original:

-
- Imagem original -
-
- )} -
-
- - {/* Editor de Imagem */} - - - 2. Editar Imagem - - - {imagemOriginal ? ( - setImagemEditada(novaUrl)} - > - - - ) : ( -
- -

Selecione uma imagem primeiro

-
- )} -
-
-
- - {/* Resultado */} - {imagemEditada && ( - - - 3. Resultado - - -
-
-
-

Imagem Original:

-
- Imagem original -
-
-
-

Imagem Editada:

-
- Imagem editada -
-
-
- -
- - -
-
-
-
- )} - - {/* Funcionalidades */} - - - Funcionalidades Disponíveis - - -
-
-

Redimensionamento

-
    -
  • • Ajustar largura e altura
  • -
  • • Manter proporção
  • -
  • • Controlar qualidade
  • -
-
-
-

Recorte

-
    -
  • • Definir área de recorte
  • -
  • • Coordenadas precisas
  • -
  • • Validação de limites
  • -
-
-
-

Efeito Blur

-
    -
  • • Intensidade ajustável
  • -
  • • Efeito suave
  • -
  • • Ideal para fundos
  • -
-
-
-

Figurinhas

-
    -
  • • Adicionar imagens sobrepostas
  • -
  • • Controlar posição e tamanho
  • -
  • • Ajustar opacidade
  • -
-
-
-
-
-
-
- ) -} diff --git a/src/app/api/image-editor/route.ts b/src/app/api/image-editor/route.ts deleted file mode 100644 index f27646af..00000000 --- a/src/app/api/image-editor/route.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { type NextRequest, NextResponse } from 'next/server' -import { - redimensionarImagem, - adicionarFigurinha, - aplicarBlur, - recortarImagem, - obterInfoImagem -} from '@/lib/image-editor' -import { z } from 'zod' - -// Schema de validação para redimensionamento -const resizeSchema = z.object({ - caminhoEntrada: z.string().min(1), - larguraDesejada: z.number().min(1).max(4000), - alturaDesejada: z.number().min(1).max(4000), - manterProporcao: z.boolean().default(true), - qualidade: z.number().min(10).max(100).default(90) -}) - -// Schema de validação para figurinhas -const stickerSchema = z.object({ - caminhoFotoPrincipal: z.string().min(1), - caminhoFigurinha: z.string().min(1), - posicaoX: z.number().min(0), - posicaoY: z.number().min(0), - larguraFigurinha: z.number().min(1).optional(), - opacidade: z.number().min(0.1).max(1.0).default(1.0) -}) - -// Schema de validação para blur -const blurSchema = z.object({ - caminhoEntrada: z.string().min(1), - intensidade: z.number().min(1).max(50).default(10) -}) - -// Schema de validação para recorte -const cropSchema = z.object({ - caminhoEntrada: z.string().min(1), - x: z.number().min(0), - y: z.number().min(0), - largura: z.number().min(1), - altura: z.number().min(1) -}) - -// Schema de validação para informações da imagem -const infoSchema = z.object({ - caminhoEntrada: z.string().min(1) -}) - -export async function POST(request: NextRequest) { - try { - const body = await request.json() as { operacao: string; [key: string]: unknown } - const { operacao } = body - - if (!operacao) { - return NextResponse.json( - { success: false, erro: 'Operação não especificada' }, - { status: 400 } - ) - } - - switch (operacao) { - case 'resize': { - const params = resizeSchema.parse(body) - const resultado = await redimensionarImagem({ - caminhoEntrada: params.caminhoEntrada, - larguraDesejada: params.larguraDesejada, - alturaDesejada: params.alturaDesejada, - manterProporcao: params.manterProporcao, - qualidade: params.qualidade - }) - return NextResponse.json(resultado) - } - - case 'sticker': { - const params = stickerSchema.parse(body) - const resultado = await adicionarFigurinha({ - caminhoFotoPrincipal: params.caminhoFotoPrincipal, - caminhoFigurinha: params.caminhoFigurinha, - posicaoX: params.posicaoX, - posicaoY: params.posicaoY, - larguraFigurinha: params.larguraFigurinha, - opacidade: params.opacidade - }) - return NextResponse.json(resultado) - } - - case 'blur': { - const params = blurSchema.parse(body) - const resultado = await aplicarBlur(params.caminhoEntrada, params.intensidade) - return NextResponse.json(resultado) - } - - case 'crop': { - const params = cropSchema.parse(body) - const resultado = await recortarImagem( - params.caminhoEntrada, - params.x, - params.y, - params.largura, - params.altura - ) - return NextResponse.json(resultado) - } - - case 'info': { - const params = infoSchema.parse(body) - const resultado = await obterInfoImagem(params.caminhoEntrada) - return NextResponse.json(resultado) - } - - default: - return NextResponse.json( - { success: false, erro: 'Operação não suportada' }, - { status: 400 } - ) - } - } catch (error) { - console.error('Erro na API de edição de imagem:', error) - - if (error instanceof z.ZodError) { - return NextResponse.json( - { - success: false, - erro: 'Dados inválidos', - detalhes: error.errors - }, - { status: 400 } - ) - } - - return NextResponse.json( - { - success: false, - erro: error instanceof Error ? error.message : 'Erro interno do servidor' - }, - { status: 500 } - ) - } -} - -export async function GET() { - return NextResponse.json({ - message: 'API de Edição de Imagem', - operacoes: ['resize', 'sticker', 'blur', 'crop', 'info'], - versao: '1.0.0' - }) -} diff --git a/src/components/image-editor.tsx b/src/components/image-editor.tsx deleted file mode 100644 index 5c6e886e..00000000 --- a/src/components/image-editor.tsx +++ /dev/null @@ -1,581 +0,0 @@ -"use client" - -import { useState, useRef } from "react" -import { Button } from "@/components/ui/button" -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" -import { Slider } from "@/components/ui/slider" -import { Switch } from "@/components/ui/switch" -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger -} from "@/components/ui/dialog" -import { - Crop, - Image as ImageIcon, - Maximize2, - Smile, - Upload, - Download, - RotateCcw, - Zap, - Loader2 -} from "lucide-react" -import { useToast } from "@/hooks/use-toast" -import { useImageEditor } from "@/hooks/use-image-editor" -import Image from "next/image" - -interface ImageEditorProps { - imagemOriginal?: string - onImagemEditada?: (caminhoSaida: string) => void - children?: React.ReactNode -} - -interface CropArea { - x: number - y: number - width: number - height: number -} - -export function ImageEditor({ imagemOriginal, onImagemEditada, children }: ImageEditorProps) { - const [imagemAtual, setImagemAtual] = useState(imagemOriginal) - const [cropArea, setCropArea] = useState({ x: 0, y: 0, width: 100, height: 100 }) - const [resizeParams, setResizeParams] = useState({ - largura: 800, - altura: 600, - manterProporcao: true, - qualidade: 90 - }) - const [stickerParams, setStickerParams] = useState({ - posicaoX: 0, - posicaoY: 0, - larguraFigurinha: 100, - opacidade: 1.0 - }) - const [blurIntensidade, setBlurIntensidade] = useState(10) - const [figurinhaFile, setFigurinhaFile] = useState(null) - const [figurinhaPreview, setFigurinhaPreview] = useState(null) - - const fileInputRef = useRef(null) - const stickerInputRef = useRef(null) - const { toast } = useToast() - const { - isProcessing, - redimensionarImagem, - adicionarFigurinha, - aplicarBlur, - recortarImagem - } = useImageEditor() - - const handleFileUpload = (event: React.ChangeEvent) => { - const file = event.target.files?.[0] - if (file?.type.startsWith('image/')) { - const url = URL.createObjectURL(file) - setImagemAtual(url) - } - } - - const handleStickerUpload = (event: React.ChangeEvent) => { - const file = event.target.files?.[0] - if (file?.type.startsWith('image/')) { - setFigurinhaFile(file) - const url = URL.createObjectURL(file) - setFigurinhaPreview(url) - } - } - - const handleResize = async () => { - if (!imagemAtual) return - - const resultado = await redimensionarImagem({ - caminhoEntrada: imagemAtual, - larguraDesejada: resizeParams.largura, - alturaDesejada: resizeParams.altura, - manterProporcao: resizeParams.manterProporcao, - qualidade: resizeParams.qualidade - }) - - if (resultado.success && resultado.caminhoSaida) { - setImagemAtual(resultado.caminhoSaida) - onImagemEditada?.(resultado.caminhoSaida) - } - } - - const handleCrop = async () => { - if (!imagemAtual) return - - const resultado = await recortarImagem({ - caminhoEntrada: imagemAtual, - x: cropArea.x, - y: cropArea.y, - largura: cropArea.width, - altura: cropArea.height - }) - - if (resultado.success && resultado.caminhoSaida) { - setImagemAtual(resultado.caminhoSaida) - onImagemEditada?.(resultado.caminhoSaida) - } - } - - const handleBlur = async () => { - if (!imagemAtual) return - - const resultado = await aplicarBlur({ - caminhoEntrada: imagemAtual, - intensidade: blurIntensidade - }) - - if (resultado.success && resultado.caminhoSaida) { - setImagemAtual(resultado.caminhoSaida) - onImagemEditada?.(resultado.caminhoSaida) - } - } - - const handleAddSticker = async () => { - if (!figurinhaFile || !imagemAtual) { - toast({ - title: "Erro", - description: "Selecione uma figurinha e uma imagem primeiro", - variant: "destructive", - }) - return - } - - // Em produção, seria necessário fazer upload da figurinha primeiro - // Por agora, vamos simular com um caminho - const resultado = await adicionarFigurinha({ - caminhoFotoPrincipal: imagemAtual, - caminhoFigurinha: figurinhaPreview ?? '', - posicaoX: stickerParams.posicaoX, - posicaoY: stickerParams.posicaoY, - larguraFigurinha: stickerParams.larguraFigurinha, - opacidade: stickerParams.opacidade - }) - - if (resultado.success && resultado.caminhoSaida) { - setImagemAtual(resultado.caminhoSaida) - onImagemEditada?.(resultado.caminhoSaida) - } - } - - const resetImage = () => { - setImagemAtual(imagemOriginal) - } - - return ( - - - {children ?? ( - - )} - - - - Editor de Imagem - - Edite suas imagens com ferramentas de recorte, redimensionamento e adição de figurinhas - - - -
- {/* Preview da Imagem */} -
- - - Preview - - - {imagemAtual ? ( -
- Imagem sendo editada -
- ) : ( -
-
- -

Nenhuma imagem selecionada

-
-
- )} -
-
- - {/* Upload de Nova Imagem */} - - - Carregar Imagem - - -
- - - - {imagemAtual && ( - - )} -
-
-
-
- - {/* Ferramentas de Edição */} -
- - - Redimensionar - Recortar - Blur - Figurinha - - - {/* Redimensionamento */} - - - - Redimensionar - - -
-
- - setResizeParams(prev => ({ - ...prev, - largura: parseInt(e.target.value) || 0 - }))} - /> -
-
- - setResizeParams(prev => ({ - ...prev, - altura: parseInt(e.target.value) || 0 - }))} - /> -
-
- -
- - setResizeParams(prev => ({ - ...prev, - qualidade: value ?? 90 - }))} - max={100} - min={10} - step={10} - className="w-full" - /> -
- -
- setResizeParams(prev => ({ - ...prev, - manterProporcao: checked - }))} - /> - -
- - -
-
-
- - {/* Recorte */} - - - - Recortar - - -
-
- - setCropArea(prev => ({ - ...prev, - x: parseInt(e.target.value) || 0 - }))} - /> -
-
- - setCropArea(prev => ({ - ...prev, - y: parseInt(e.target.value) || 0 - }))} - /> -
-
- -
-
- - setCropArea(prev => ({ - ...prev, - width: parseInt(e.target.value) || 0 - }))} - /> -
-
- - setCropArea(prev => ({ - ...prev, - height: parseInt(e.target.value) || 0 - }))} - /> -
-
- - -
-
-
- - {/* Blur */} - - - - Efeito Blur - - -
- - setBlurIntensidade(value ?? 10)} - max={50} - min={1} - step={1} - className="w-full" - /> -
- - -
-
-
- - {/* Figurinhas */} - - - - Adicionar Figurinha - - - {/* Upload de Figurinha */} -
- - - - - {figurinhaPreview && ( -
- Preview da figurinha -
- )} -
- - {/* Posicionamento */} -
-
- - setStickerParams(prev => ({ - ...prev, - posicaoX: parseInt(e.target.value) || 0 - }))} - /> -
-
- - setStickerParams(prev => ({ - ...prev, - posicaoY: parseInt(e.target.value) || 0 - }))} - /> -
-
- - {/* Tamanho e Opacidade */} -
-
- - setStickerParams(prev => ({ - ...prev, - larguraFigurinha: parseInt(e.target.value) || 0 - }))} - /> -
-
- - setStickerParams(prev => ({ - ...prev, - opacidade: value ?? 1.0 - }))} - max={1} - min={0.1} - step={0.1} - className="w-full" - /> -
-
- - -
-
-
-
-
-
- - - - -
-
- ) -} diff --git a/src/components/ui/multiple-image-upload.tsx b/src/components/ui/multiple-image-upload.tsx index 46ba8c45..590e1ef3 100644 --- a/src/components/ui/multiple-image-upload.tsx +++ b/src/components/ui/multiple-image-upload.tsx @@ -1,14 +1,13 @@ "use client" import { useState, useCallback } from "react" -import { Upload, X, Edit } from "lucide-react" +import { Upload, X } from "lucide-react" import { Button } from "@/components/ui/button" import { Card } from "@/components/ui/card" import Image from "next/image" import { cn } from "@/lib/utils" import { useUploadThing } from "@/components/uploadthing" import { deleteFiles } from "@/components/uploadthing" -import { ImageEditor } from "@/components/image-editor" import type { ClientUploadedFileData } from "uploadthing/types" interface MultipleImageUploadProps { @@ -24,7 +23,6 @@ export function MultipleImageUpload({ }: MultipleImageUploadProps) { const [images, setImages] = useState([]) const [isUploading, setIsUploading] = useState(false) - const [editingImageIndex, setEditingImageIndex] = useState(null) const { startUpload } = useUploadThing("multipleImageUploader", { onClientUploadComplete: (res: ClientUploadedFileData[]) => { @@ -81,15 +79,6 @@ export function MultipleImageUpload({ onImagesChange(newImages) }, [images, onImagesChange]) - const handleImageEdited = useCallback((newImageUrl: string) => { - if (editingImageIndex !== null) { - const newImages = [...images] - newImages[editingImageIndex] = newImageUrl - setImages(newImages) - onImagesChange(newImages) - setEditingImageIndex(null) - } - }, [editingImageIndex, images, onImagesChange]) const handleDrop = useCallback((event: React.DragEvent) => { event.preventDefault() @@ -176,24 +165,6 @@ export function MultipleImageUpload({ {/* Botões de ação */}
- {/* Botão de editar */} - - - - {/* Botão de remover */}
- {/* Texto de tamanho recomendado */} -

- Tamanho recomendado: 515 x 300 -

- {files.length > 0 && fileUrl && (
- { - setFileUrl(novaUrl) - onImageUrlGenerated(novaUrl) - }} - > - - -
)} - - {/* Crop obrigatório quando imagem é muito alta */} - {cropRequired && tempImageUrl && ( -
- -
- )} - - {/* Modal obrigatório para crop */} - - - - Corte de Imagem Obrigatório - - A imagem possui mais de 300px de altura. Você deve cortar a imagem para continuar. - O tamanho recomendado é 515 x 300 pixels. - - - - - - - - - ); } \ No newline at end of file diff --git a/src/hooks/use-image-dimensions.ts b/src/hooks/use-image-dimensions.ts deleted file mode 100644 index 7986b2c2..00000000 --- a/src/hooks/use-image-dimensions.ts +++ /dev/null @@ -1,76 +0,0 @@ -"use client" - -import { useState, useCallback } from "react" - -interface ImageDimensions { - width: number - height: number -} - -interface UseImageDimensionsReturn { - dimensions: ImageDimensions | null - isLoading: boolean - error: string | null - checkDimensions: (imageUrl: string) => Promise - reset: () => void -} - -export function useImageDimensions(): UseImageDimensionsReturn { - const [dimensions, setDimensions] = useState(null) - const [isLoading, setIsLoading] = useState(false) - const [error, setError] = useState(null) - - const checkDimensions = useCallback(async (imageUrl: string): Promise => { - if (!imageUrl) { - setError("URL da imagem não fornecida") - return null - } - - setIsLoading(true) - setError(null) - - try { - const img = new Image() - - return new Promise((resolve, reject) => { - img.onload = () => { - const dims = { - width: img.naturalWidth, - height: img.naturalHeight - } - setDimensions(dims) - setIsLoading(false) - resolve(dims) - } - - img.onerror = () => { - const errorMsg = "Erro ao carregar imagem" - setError(errorMsg) - setIsLoading(false) - reject(new Error(errorMsg)) - } - - img.src = imageUrl - }) - } catch (err) { - const errorMsg = err instanceof Error ? err.message : "Erro desconhecido" - setError(errorMsg) - setIsLoading(false) - return null - } - }, []) - - const reset = useCallback(() => { - setDimensions(null) - setError(null) - setIsLoading(false) - }, []) - - return { - dimensions, - isLoading, - error, - checkDimensions, - reset - } -} diff --git a/src/hooks/use-image-editor.ts b/src/hooks/use-image-editor.ts deleted file mode 100644 index dc08dbe2..00000000 --- a/src/hooks/use-image-editor.ts +++ /dev/null @@ -1,205 +0,0 @@ -import { useState, useCallback } from 'react' -import { useToast } from '@/hooks/use-toast' - -interface ImageOperationResult { - success: boolean - caminhoSaida?: string - erro?: string - dimensoes?: { - largura: number - altura: number - } -} - -interface ImageInfo { - success: boolean - largura?: number - altura?: number - formato?: string - erro?: string -} - -interface ResizeParams { - caminhoEntrada: string - larguraDesejada: number - alturaDesejada: number - manterProporcao: boolean - qualidade?: number -} - -interface StickerParams { - caminhoFotoPrincipal: string - caminhoFigurinha: string - posicaoX: number - posicaoY: number - larguraFigurinha?: number - opacidade?: number -} - -interface CropParams { - caminhoEntrada: string - x: number - y: number - largura: number - altura: number -} - -interface BlurParams { - caminhoEntrada: string - intensidade: number -} - -export function useImageEditor() { - const [isProcessing, setIsProcessing] = useState(false) - const { toast } = useToast() - - const processImage = useCallback(async ( - operacao: string, - params: ResizeParams | StickerParams | BlurParams | CropParams - ): Promise => { - setIsProcessing(true) - - try { - const response = await fetch('/api/image-editor', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - operacao, - ...params - }) - }) - - const resultado = await response.json() as ImageOperationResult - - if (!response.ok) { - throw new Error(resultado.erro ?? 'Erro na requisição') - } - - return resultado - } catch (error) { - const erro = error instanceof Error ? error.message : 'Erro desconhecido' - return { - success: false, - erro - } - } finally { - setIsProcessing(false) - } - }, []) - - const redimensionarImagem = useCallback(async (params: ResizeParams): Promise => { - const resultado = await processImage('resize', params) - - if (resultado.success) { - toast({ - title: "Imagem redimensionada", - description: "A imagem foi redimensionada com sucesso!", - }) - } else { - toast({ - title: "Erro", - description: resultado.erro ?? "Erro ao redimensionar imagem", - variant: "destructive", - }) - } - - return resultado - }, [processImage, toast]) - - const adicionarFigurinha = useCallback(async (params: StickerParams): Promise => { - const resultado = await processImage('sticker', params) - - if (resultado.success) { - toast({ - title: "Figurinha adicionada", - description: "A figurinha foi adicionada com sucesso!", - }) - } else { - toast({ - title: "Erro", - description: resultado.erro ?? "Erro ao adicionar figurinha", - variant: "destructive", - }) - } - - return resultado - }, [processImage, toast]) - - const aplicarBlur = useCallback(async (params: BlurParams): Promise => { - const resultado = await processImage('blur', params) - - if (resultado.success) { - toast({ - title: "Blur aplicado", - description: "O efeito blur foi aplicado com sucesso!", - }) - } else { - toast({ - title: "Erro", - description: resultado.erro ?? "Erro ao aplicar blur", - variant: "destructive", - }) - } - - return resultado - }, [processImage, toast]) - - const recortarImagem = useCallback(async (params: CropParams): Promise => { - const resultado = await processImage('crop', params) - - if (resultado.success) { - toast({ - title: "Imagem recortada", - description: "A imagem foi recortada com sucesso!", - }) - } else { - toast({ - title: "Erro", - description: resultado.erro ?? "Erro ao recortar imagem", - variant: "destructive", - }) - } - - return resultado - }, [processImage, toast]) - - const obterInfoImagem = useCallback(async (caminhoEntrada: string): Promise => { - try { - const response = await fetch('/api/image-editor', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - operacao: 'info', - caminhoEntrada - }) - }) - - const resultado = await response.json() as ImageInfo - - if (!response.ok) { - throw new Error(resultado.erro ?? 'Erro na requisição') - } - - return resultado - } catch (error) { - const erro = error instanceof Error ? error.message : 'Erro desconhecido' - return { - success: false, - erro - } - } - }, []) - - return { - isProcessing, - redimensionarImagem, - adicionarFigurinha, - aplicarBlur, - recortarImagem, - obterInfoImagem - } -} diff --git a/src/lib/image-editor.ts b/src/lib/image-editor.ts deleted file mode 100644 index bb5279de..00000000 --- a/src/lib/image-editor.ts +++ /dev/null @@ -1,301 +0,0 @@ -import Jimp from 'jimp' -import { join } from 'path' -import { tmpdir } from 'os' - -/** - * Interface para parâmetros de redimensionamento - */ -export interface ResizeParams { - caminhoEntrada: string - larguraDesejada: number - alturaDesejada: number - manterProporcao: boolean - qualidade?: number -} - -/** - * Interface para parâmetros de adição de figurinhas - */ -export interface StickerParams { - caminhoFotoPrincipal: string - caminhoFigurinha: string - posicaoX: number - posicaoY: number - larguraFigurinha?: number - opacidade?: number -} - -/** - * Interface para resultado das operações - */ -export interface ImageOperationResult { - success: boolean - caminhoSaida?: string - erro?: string - dimensoes?: { - largura: number - altura: number - } -} - -/** - * Redimensiona uma imagem usando Jimp - * - * @param params - Parâmetros de redimensionamento - * @returns Resultado da operação - */ -export async function redimensionarImagem(params: ResizeParams): Promise { - try { - const { caminhoEntrada, larguraDesejada, alturaDesejada, manterProporcao, qualidade = 90 } = params - - // Ler a imagem original - const imagem = await Jimp.read(caminhoEntrada) - - // Aplicar redimensionamento baseado na flag manterProporcao - if (manterProporcao) { - // Manter proporção - usar a dimensão mais restritiva - const proporcaoOriginal = imagem.bitmap.width / imagem.bitmap.height - const proporcaoDesejada = larguraDesejada / alturaDesejada - - if (proporcaoOriginal > proporcaoDesejada) { - // Imagem é mais larga - limitar pela largura - imagem.resize(larguraDesejada, Jimp.AUTO) - } else { - // Imagem é mais alta - limitar pela altura - imagem.resize(Jimp.AUTO, alturaDesejada) - } - } else { - // Redimensionamento livre (pode distorcer) - imagem.resize(larguraDesejada, alturaDesejada) - } - - // Aplicar qualidade se especificada - if (qualidade && qualidade > 0 && qualidade <= 100) { - imagem.quality(qualidade) - } - - // Gerar caminho de saída único - const timestamp = Date.now() - const extensao = caminhoEntrada.split('.').pop() ?? 'jpg' - const caminhoSaida = join(tmpdir(), `imagem_redimensionada_${timestamp}.${extensao}`) - - // Salvar a imagem redimensionada - await imagem.writeAsync(caminhoSaida) - - return { - success: true, - caminhoSaida, - dimensoes: { - largura: imagem.bitmap.width, - altura: imagem.bitmap.height - } - } - } catch (error) { - return { - success: false, - erro: error instanceof Error ? error.message : 'Erro desconhecido ao redimensionar imagem' - } - } -} - -/** - * Adiciona uma figurinha (segunda imagem) sobre a imagem principal - * - * @param params - Parâmetros de adição de figurinha - * @returns Resultado da operação - */ -export async function adicionarFigurinha(params: StickerParams): Promise { - try { - const { - caminhoFotoPrincipal, - caminhoFigurinha, - posicaoX, - posicaoY, - larguraFigurinha, - opacidade = 1.0 - } = params - - // Ler ambas as imagens - const fotoPrincipal = await Jimp.read(caminhoFotoPrincipal) - const figurinha = await Jimp.read(caminhoFigurinha) - - // Redimensionar figurinha se especificado - if (larguraFigurinha && larguraFigurinha > 0) { - figurinha.resize(larguraFigurinha, Jimp.AUTO) - } - - // Aplicar opacidade se especificada - if (opacidade !== 1.0 && opacidade > 0 && opacidade <= 1.0) { - figurinha.opacity(opacidade) - } - - // Verificar se a figurinha cabe na imagem principal - const larguraFigurinhaFinal = figurinha.bitmap.width - const alturaFigurinhaFinal = figurinha.bitmap.height - const larguraPrincipal = fotoPrincipal.bitmap.width - const alturaPrincipal = fotoPrincipal.bitmap.height - - if (posicaoX + larguraFigurinhaFinal > larguraPrincipal || - posicaoY + alturaFigurinhaFinal > alturaPrincipal) { - return { - success: false, - erro: 'A figurinha excede os limites da imagem principal' - } - } - - // Composição (mesclagem) das imagens - fotoPrincipal.composite(figurinha, posicaoX, posicaoY) - - // Gerar caminho de saída único - const timestamp = Date.now() - const extensao = caminhoFotoPrincipal.split('.').pop() ?? 'jpg' - const caminhoSaida = join(tmpdir(), `imagem_com_figurinha_${timestamp}.${extensao}`) - - // Salvar a imagem final - await fotoPrincipal.writeAsync(caminhoSaida) - - return { - success: true, - caminhoSaida, - dimensoes: { - largura: fotoPrincipal.bitmap.width, - altura: fotoPrincipal.bitmap.height - } - } - } catch (error) { - return { - success: false, - erro: error instanceof Error ? error.message : 'Erro desconhecido ao adicionar figurinha' - } - } -} - -/** - * Aplica um efeito de blur na imagem - * - * @param caminhoEntrada - Caminho da imagem original - * @param intensidade - Intensidade do blur (0-100) - * @returns Resultado da operação - */ -export async function aplicarBlur(caminhoEntrada: string, intensidade = 10): Promise { - try { - const imagem = await Jimp.read(caminhoEntrada) - - // Aplicar blur - imagem.blur(intensidade) - - // Gerar caminho de saída único - const timestamp = Date.now() - const extensao = caminhoEntrada.split('.').pop() ?? 'jpg' - const caminhoSaida = join(tmpdir(), `imagem_blur_${timestamp}.${extensao}`) - - // Salvar a imagem com blur - await imagem.writeAsync(caminhoSaida) - - return { - success: true, - caminhoSaida, - dimensoes: { - largura: imagem.bitmap.width, - altura: imagem.bitmap.height - } - } - } catch (error) { - return { - success: false, - erro: error instanceof Error ? error.message : 'Erro desconhecido ao aplicar blur' - } - } -} - -/** - * Recorta uma imagem em uma área específica - * - * @param caminhoEntrada - Caminho da imagem original - * @param x - Posição X do recorte - * @param y - Posição Y do recorte - * @param largura - Largura do recorte - * @param altura - Altura do recorte - * @returns Resultado da operação - */ -export async function recortarImagem( - caminhoEntrada: string, - x: number, - y: number, - largura: number, - altura: number -): Promise { - try { - const imagem = await Jimp.read(caminhoEntrada) - - // Verificar se as dimensões do recorte são válidas - if (x < 0 || y < 0 || largura <= 0 || altura <= 0) { - return { - success: false, - erro: 'Dimensões de recorte inválidas' - } - } - - if (x + largura > imagem.bitmap.width || y + altura > imagem.bitmap.height) { - return { - success: false, - erro: 'O recorte excede os limites da imagem original' - } - } - - // Aplicar recorte - imagem.crop(x, y, largura, altura) - - // Gerar caminho de saída único - const timestamp = Date.now() - const extensao = caminhoEntrada.split('.').pop() ?? 'jpg' - const caminhoSaida = join(tmpdir(), `imagem_recortada_${timestamp}.${extensao}`) - - // Salvar a imagem recortada - await imagem.writeAsync(caminhoSaida) - - return { - success: true, - caminhoSaida, - dimensoes: { - largura: imagem.bitmap.width, - altura: imagem.bitmap.height - } - } - } catch (error) { - return { - success: false, - erro: error instanceof Error ? error.message : 'Erro desconhecido ao recortar imagem' - } - } -} - -/** - * Obtém informações sobre uma imagem - * - * @param caminhoEntrada - Caminho da imagem - * @returns Informações da imagem - */ -export async function obterInfoImagem(caminhoEntrada: string): Promise<{ - success: boolean - largura?: number - altura?: number - formato?: string - erro?: string -}> { - try { - const imagem = await Jimp.read(caminhoEntrada) - - return { - success: true, - largura: imagem.bitmap.width, - altura: imagem.bitmap.height, - formato: imagem.getMIME() - } - } catch (error) { - return { - success: false, - erro: error instanceof Error ? error.message : 'Erro desconhecido ao obter informações da imagem' - } - } -}