diff --git a/src/components/video-editor/AnnotationOverlay.tsx b/src/components/video-editor/AnnotationOverlay.tsx index f6c9937..3120f0b 100644 --- a/src/components/video-editor/AnnotationOverlay.tsx +++ b/src/components/video-editor/AnnotationOverlay.tsx @@ -9,6 +9,8 @@ import { DEFAULT_BLUR_INTENSITY, } from "./types"; +const FREEHAND_POINT_THRESHOLD = 1; + function buildBlurPolygonClipPath(points: Array<{ x: number; y: number }>) { if (points.length < 3) return undefined; const polygon = points.map((point) => `${point.x}% ${point.y}%`).join(", "); @@ -53,7 +55,7 @@ export function AnnotationOverlay({ const y = (annotation.position.y / 100) * containerHeight; const width = (annotation.size.width / 100) * containerWidth; const height = (annotation.size.height / 100) * containerHeight; - const blurShape = annotation.type === "blur" ? annotation.blurData?.shape || "rectangle" : null; + const blurShape = annotation.type === "blur" ? (annotation.blurData?.shape ?? "rectangle") : null; const isSelectedFreehandBlur = isSelected && blurShape === "freehand"; const isDraggingRef = useRef(false); const isDrawingFreehandRef = useRef(false); @@ -92,8 +94,8 @@ export function AnnotationOverlay({ } const dx = point.x - lastPoint.x; const dy = point.y - lastPoint.y; - // Keep enough points to follow the cursor closely. - if (Math.hypot(dx, dy) >= 0.03) { + // Sample freehand points in annotation-space percent units to avoid overly dense paths. + if (Math.hypot(dx, dy) >= FREEHAND_POINT_THRESHOLD) { points.push(point); } }; @@ -233,7 +235,7 @@ export function AnnotationOverlay({ ); case "blur": { - const shape = blurShape || "rectangle"; + const shape = annotation.blurData?.shape ?? "rectangle"; const blurIntensity = Math.max( 1, Math.round(annotation.blurData?.intensity ?? DEFAULT_BLUR_INTENSITY), diff --git a/src/components/video-editor/timeline/TimelineEditor.tsx b/src/components/video-editor/timeline/TimelineEditor.tsx index 443ae12..5ef8c7b 100644 --- a/src/components/video-editor/timeline/TimelineEditor.tsx +++ b/src/components/video-editor/timeline/TimelineEditor.tsx @@ -734,7 +734,7 @@ function Timeline({ span={item.span} isSelected={item.id === selectedBlurId} onSelect={() => onSelectBlur?.(item.id)} - variant="annotation" + variant={item.variant} > {item.label} diff --git a/src/i18n/locales/tr/settings.json b/src/i18n/locales/tr/settings.json index 51f82fd..936f75c 100644 --- a/src/i18n/locales/tr/settings.json +++ b/src/i18n/locales/tr/settings.json @@ -98,7 +98,7 @@ "typeText": "Metin", "typeImage": "Görüntü", "typeArrow": "Ok", - "typeBlur": "Bulanik", + "typeBlur": "Bulanık", "textContent": "Metin İçeriği", "textPlaceholder": "Metninizi girin...", "fontStyle": "Yazı Tipi Stili", @@ -115,9 +115,9 @@ "arrowDirection": "Ok Yönü", "strokeWidth": "Çizgi Kalınlığı: {{width}}px", "arrowColor": "Ok Rengi", - "blurShape": "Bulanik Sekli", - "blurIntensity": "Bulaniklik Yogunlugu", - "blurShapeRectangle": "Dikdortgen", + "blurShape": "Bulanık Şekli", + "blurIntensity": "Bulanıklık Yoğunluğu", + "blurShapeRectangle": "Dikdörtgen", "blurShapeOval": "Oval", "blurShapeFreehand": "Serbest", "deleteAnnotation": "Açıklamayı Sil", diff --git a/src/i18n/locales/tr/timeline.json b/src/i18n/locales/tr/timeline.json index 378efdf..46aa2c5 100644 --- a/src/i18n/locales/tr/timeline.json +++ b/src/i18n/locales/tr/timeline.json @@ -5,14 +5,14 @@ "addTrim": "Kırpma Ekle (T)", "addAnnotation": "Açıklama Ekle (A)", "addSpeed": "Hız Ekle (S)", - "addBlur": "Bulanik ekle" + "addBlur": "Bulanık ekle" }, "hints": { "pressZoom": "Yakınlaştırma eklemek için Z tuşuna basın", "pressTrim": "Kırpma eklemek için T tuşuna basın", "pressAnnotation": "Açıklama eklemek için A tuşuna basın", "pressSpeed": "Hız eklemek için S tuşuna basın", - "pressBlur": "Aractan bulanik bolgeleri ekleyin" + "pressBlur": "Araçtan bulanık bölgeleri ekleyin" }, "labels": { "pan": "Kaydır", @@ -23,7 +23,7 @@ "annotationItem": "Açıklama", "imageItem": "Görüntü", "emptyText": "Boş metin", - "blurItem": "Bulanik {{index}}" + "blurItem": "Bulanık {{index}}" }, "emptyState": { "noVideo": "Video Yüklenmedi", diff --git a/src/lib/exporter/annotationRenderer.ts b/src/lib/exporter/annotationRenderer.ts index e440a34..ec663e8 100644 --- a/src/lib/exporter/annotationRenderer.ts +++ b/src/lib/exporter/annotationRenderer.ts @@ -6,6 +6,9 @@ import { MIN_BLUR_INTENSITY, } from "@/components/video-editor/types"; +let blurScratchCanvas: HTMLCanvasElement | null = null; +let blurScratchCtx: CanvasRenderingContext2D | null = null; + // SVG path data for each arrow direction const ARROW_PATHS: Record = { up: ["M 50 20 L 50 80", "M 50 20 L 35 35", "M 50 20 L 65 35"], @@ -165,18 +168,22 @@ function renderBlur( const sh = Math.max(0, ey - sy); if (sw <= 0 || sh <= 0) return; - const tempCanvas = document.createElement("canvas"); - tempCanvas.width = sw; - tempCanvas.height = sh; - const tempCtx = tempCanvas.getContext("2d"); - if (!tempCtx) return; - tempCtx.drawImage(canvas, sx, sy, sw, sh, 0, 0, sw, sh); + if (!blurScratchCanvas || !blurScratchCtx) { + blurScratchCanvas = document.createElement("canvas"); + blurScratchCtx = blurScratchCanvas.getContext("2d"); + } + if (!blurScratchCanvas || !blurScratchCtx) return; + + blurScratchCanvas.width = sw; + blurScratchCanvas.height = sh; + blurScratchCtx.clearRect(0, 0, sw, sh); + blurScratchCtx.drawImage(canvas, sx, sy, sw, sh, 0, 0, sw, sh); ctx.save(); drawBlurPath(ctx, annotation, x, y, width, height); ctx.clip(); ctx.filter = `blur(${blurRadius}px)`; - ctx.drawImage(tempCanvas, sx, sy); + ctx.drawImage(blurScratchCanvas, sx, sy); ctx.filter = "none"; ctx.restore(); }