Gartic.io için otomatik resim çizim botu. Görsel yükle, modu seç, otomatik çiz.
// ==UserScript==
// @name Gartic Auto Draw Bot
// @namespace https://greasyfork.org/users/zerask
// @version 1.0.0
// @description Gartic.io için otomatik resim çizim botu. Görsel yükle, modu seç, otomatik çiz.
// @author zerask
// @match https://gartic.io/*
// @grant none
// @license MIT
// @icon https://www.google.com/s2/favicons?sz=64&domain=gartic.io
// ==/UserScript==
(function () {
"use strict";
const CW = 767, CH = 448;
const CFG = { delay: 120 };
// state
let timeoutIds = new Set();
let drawing = false;
let globalSegs = [];
let globalColorMode = false;
let lastIndex = 0;
// vector state
let vectorAnimationId = null;
let vectorDrawCancel = false;
// --- yardımcı fonksiyonlar ---
function hexToRgb(hex) {
const r = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return r ? { r: parseInt(r[1],16), g: parseInt(r[2],16), b: parseInt(r[3],16) } : null;
}
function sampleColorClassic(x1, y1, x2, rgba) {
const mx = Math.max(0, Math.min(CW-1, Math.round((x1+x2)/2)));
const my = Math.max(0, Math.min(CH-1, Math.round(y1)));
const i = (my * CW + mx) * 4;
return { r: rgba[i], g: rgba[i+1], b: rgba[i+2] };
}
function processImageClassic(imgEl, threshold, contrast) {
const cv = document.createElement("canvas");
cv.width = CW; cv.height = CH;
const ctx = cv.getContext("2d");
const scale = Math.min(CW / imgEl.naturalWidth, CH / imgEl.naturalHeight);
const w = imgEl.naturalWidth * scale;
const h = imgEl.naturalHeight * scale;
const ox = (CW-w)/2, oy = (CH-h)/2;
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, CW, CH);
ctx.drawImage(imgEl, ox, oy, w, h);
const raw = ctx.getImageData(0, 0, CW, CH);
const data = raw.data;
const gray = new Uint8Array(CW * CH);
for (let i = 0; i < CW*CH; i++) {
const idx = i*4, a = data[idx+3];
const r = data[idx], g = data[idx+1], b = data[idx+2];
if (a < 128 || (r > 240 && g > 240 && b > 240)) { gray[i] = 255; }
else {
let lum = 0.299*r + 0.587*g + 0.114*b;
lum = Math.min(255, Math.max(0, ((lum-128)*contrast)+128));
gray[i] = lum;
}
}
const mask = new Uint8Array(CW*CH);
for (let i = 0; i < CW*CH; i++) mask[i] = gray[i] < threshold ? 0 : 255;
return { mask, rgba: data };
}
function collectImageSegmentsClassic(processed, step, colorMode, baseColor) {
const { mask, rgba } = processed;
const segs = [];
function tag(s) {
if (!colorMode || !baseColor) return s;
const c = sampleColorClassic(s.x1, s.y1, s.x2, rgba);
s.color = `rgb(${Math.round(c.r*0.7+baseColor.r*0.3)},${Math.round(c.g*0.7+baseColor.g*0.3)},${Math.round(c.b*0.7+baseColor.b*0.3)})`;
return s;
}
for (let y = 0; y < CH; y += step) {
let run = false, sx = 0;
for (let x = 0; x < CW; x++) {
const dark = mask[y*CW+x] < 128;
if (dark && !run) { run = true; sx = x; }
else if (!dark && run) { run = false; segs.push(tag({x1:sx,y1:y,x2:x,y2:y})); }
}
if (run) segs.push(tag({x1:sx,y1:y,x2:CW-1,y2:y}));
}
return segs;
}
// --- gartic bağlantısı ---
function getDrawer() {
const el = document.querySelector("div#answer");
if (!el) return null;
for (const k in el) {
if (!k.startsWith("__react")) continue;
const g = el[k]?.return?.stateNode?._game;
if (!g) continue;
return { d: g._desenho, rect: g._desenho._elemBase.getBoundingClientRect() };
}
return null;
}
function selectTool(toolId) {
const btn = document.querySelector(`li#${toolId}`);
if (btn && !btn.classList.contains('active')) btn.click();
}
// temizle diyalogu
function clickYesOnDialog() {
let attempts = 0;
const interval = setInterval(() => {
attempts++;
const buttons = Array.from(document.querySelectorAll('button, div[role="button"], .button, [class*="btn"]'));
const yesBtn = buttons.find(b => {
const t = b.textContent.trim().toUpperCase();
return t === 'EVET' || t === 'YES' || t === 'TAMAM' || t === 'OK';
});
if (yesBtn) {
yesBtn.click();
clearInterval(interval);
hardStop();
setStatus("🗑️ Temizlendi", false);
} else if (attempts >= 25) {
clearInterval(interval);
}
}, 120);
}
function clearCanvas() {
const btn = document.querySelector("li#clean");
if (btn) { btn.click(); clickYesOnDialog(); }
else setStatus("❌ Temizle butonu yok", false);
}
// --- durdur / devam et ---
function hardStop() {
window.__garticBotStop = true;
timeoutIds.forEach(id => clearTimeout(id));
timeoutIds.clear();
drawing = false;
const resumeBtn = document.getElementById("gu-resume");
if (resumeBtn) resumeBtn.style.display = (globalSegs.length > lastIndex) ? "block" : "none";
setStatus(`⛔ Durduruldu (${lastIndex}/${globalSegs.length})`, false);
}
function stopDrawing() { hardStop(); stopVectorDrawing(); }
function resumeDrawing() {
if (!globalSegs.length || lastIndex >= globalSegs.length) {
setStatus("⚠️ Devam edilecek çizim yok", false); return;
}
if (globalColorMode) startDrawColored(globalSegs, lastIndex);
else startDraw(globalSegs, lastIndex);
}
function sendLine(d, rect, x1, y1, x2, y2, delay, tool) {
if (window.__garticBotStop) return;
const mk = (x,y) => ({ clientX: rect.left+x, clientY: rect.top+y, preventDefault(){} });
const id = setTimeout(() => {
if (window.__garticBotStop) return;
if (tool) selectTool(tool);
d._eventMouseDown(mk(x1,y1));
d._eventMouseMove(mk(x2,y2));
d._eventMouseUp(mk(x2,y2));
}, delay);
timeoutIds.add(id);
const cleanId = setTimeout(() => timeoutIds.delete(id), delay+2000);
timeoutIds.add(cleanId);
}
// --- görüntü işleme araçları ---
function getImageData(imgEl) {
const cv = document.createElement("canvas");
cv.width = CW; cv.height = CH;
const ctx = cv.getContext("2d");
const scale = Math.min(CW/imgEl.naturalWidth, CH/imgEl.naturalHeight);
const w_ = imgEl.naturalWidth*scale, h_ = imgEl.naturalHeight*scale;
ctx.fillStyle = "#fff"; ctx.fillRect(0,0,CW,CH);
ctx.drawImage(imgEl, (CW-w_)/2, (CH-h_)/2, w_, h_);
return ctx.getImageData(0,0,CW,CH);
}
function toGray(rgba, contrast) {
const gray = new Float32Array(CW*CH);
for (let i = 0; i < CW*CH; i++) {
const idx=i*4, a=rgba[idx+3];
if (a<128) { gray[i]=255; continue; }
let lum = 0.299*rgba[idx] + 0.587*rgba[idx+1] + 0.114*rgba[idx+2];
gray[i] = Math.min(255, Math.max(0, ((lum-128)*contrast)+128));
}
return gray;
}
function gaussianBlur(gray, w, h) {
const K=[1,4,7,4,1,4,16,26,16,4,7,26,41,26,7,4,16,26,16,4,1,4,7,4,1];
const out = new Float32Array(w*h);
for (let y=2;y<h-2;y++)
for (let x=2;x<w-2;x++) {
let s=0;
for (let j=-2;j<=2;j++) for (let i=-2;i<=2;i++)
s += gray[(y+j)*w+(x+i)] * K[(j+2)*5+(i+2)];
out[y*w+x] = s/273;
}
for (let y=0;y<h;y++) for (let x=0;x<w;x++)
if (y<2||y>=h-2||x<2||x>=w-2) out[y*w+x] = gray[y*w+x];
return out;
}
function buildBackgroundMaskAuto(gray, w, h) {
const N=10; let sum=0;
for (let i=0;i<N;i++) {
sum+=gray[Math.floor(i*w/N)]; sum+=gray[(h-1)*w+Math.floor(i*w/N)];
sum+=gray[Math.floor(i*h/N)*w]; sum+=gray[Math.floor(i*h/N)*w+(w-1)];
}
const edgeAvg = sum/(N*4);
const isDarkBg = edgeAvg < 100;
const bgLevel = isDarkBg ? 55 : 220;
const isBgPx = isDarkBg ? v=>v<=bgLevel : v=>v>=bgLevel;
const bg=new Uint8Array(w*h), stack=[];
const tryAdd=(x,y)=>{ if(x<0||x>=w||y<0||y>=h)return; const i=y*w+x; if(!bg[i]&&isBgPx(gray[i])){bg[i]=1;stack.push(i);} };
for (let x=0;x<w;x++){tryAdd(x,0);tryAdd(x,h-1);}
for (let y=1;y<h-1;y++){tryAdd(0,y);tryAdd(w-1,y);}
while (stack.length) {
const i=stack.pop(), x=i%w, y=Math.floor(i/w);
if(x>0){const ni=i-1;if(!bg[ni]&&isBgPx(gray[ni])){bg[ni]=1;stack.push(ni);}}
if(x<w-1){const ni=i+1;if(!bg[ni]&&isBgPx(gray[ni])){bg[ni]=1;stack.push(ni);}}
if(y>0){const ni=i-w;if(!bg[ni]&&isBgPx(gray[ni])){bg[ni]=1;stack.push(ni);}}
if(y<h-1){const ni=i+w;if(!bg[ni]&&isBgPx(gray[ni])){bg[ni]=1;stack.push(ni);}}
}
return { bg, isDarkBg };
}
function isSkinPixel(r,g,b) {
const Y = 0.299*r+0.587*g+0.114*b;
if (Y<50||Y>235) return false;
const Cb = 128-0.16874*r-0.33126*g+0.5*b;
const Cr = 128+0.5*r-0.41869*g-0.08131*b;
return (Cb>=80&&Cb<=122)&&(Cr>=133&&Cr<=173);
}
function sampleColorNeighborhood(x1, x2, y, rgba, w) {
const mx=Math.round((x1+x2)/2);
const clamp=(v,max)=>Math.max(0,Math.min(max-1,v));
let r=0,g=0,b=0,ws=0;
const wt=[[1,2,1],[2,4,2],[1,2,1]];
for (let dy=-1;dy<=1;dy++) for (let dx=-1;dx<=1;dx++) {
const nx=clamp(mx+dx,w), ny=clamp(y+dy,CH);
const idx=(ny*w+nx)*4, w_=wt[dy+1][dx+1];
r+=rgba[idx]*w_; g+=rgba[idx+1]*w_; b+=rgba[idx+2]*w_; ws+=w_;
}
return `rgb(${Math.round(r/ws)},${Math.round(g/ws)},${Math.round(b/ws)})`;
}
function sampleColorRect(x, y, w, h, rgba, rw) {
let r=0,g=0,b=0,cnt=0;
const x2=Math.min(x+w,CW), y2=Math.min(y+h,CH);
const step=Math.max(1,Math.floor(Math.min(w,h)/4));
for (let py=y;py<y2;py+=step) for (let px=x;px<x2;px+=step) {
const i=(py*rw+px)*4; r+=rgba[i]; g+=rgba[i+1]; b+=rgba[i+2]; cnt++;
}
if (!cnt) return 'rgb(128,128,128)';
return `rgb(${Math.round(r/cnt)},${Math.round(g/cnt)},${Math.round(b/cnt)})`;
}
// --- çizgi birleştirme ---
function dist(ax,ay,bx,by) { return Math.sqrt((ax-bx)**2+(ay-by)**2); }
function angleBetween(x1,y1,x2,y2,x3,y3) {
const a1=Math.atan2(y2-y1,x2-x1), a2=Math.atan2(y3-y2,x3-x2);
let diff=Math.abs(a1-a2);
if (diff>Math.PI) diff=2*Math.PI-diff;
return diff*(180/Math.PI);
}
function mergeCollinearSegments(segs, angleThr=18, gapThr=4) {
if (!segs.length) return [];
const merged = [];
let cur = {...segs[0]};
for (let i=1;i<segs.length;i++) {
const s=segs[i];
const d=dist(cur.x2,cur.y2,s.x1,s.y1);
const ang=angleBetween(cur.x1,cur.y1,cur.x2,cur.y2,s.x1,s.y1);
if (d<=gapThr&&ang<=angleThr&&(!cur.color||cur.color===s.color)) {
cur.x2=s.x2; cur.y2=s.y2;
} else { merged.push(cur); cur={...s}; }
}
merged.push(cur);
return merged;
}
// --- FAST modu ---
function collectPortraitFill(gray, bgMask, rgba, w, h, maxStep, skipSkin, colorMode) {
const rowScore=new Float32Array(h);
for (let y=1;y<h-1;y++) {
let score=0,cnt=0;
for (let x=1;x<w-1;x++) {
if (bgMask[y*w+x]) continue; cnt++;
const gx=Math.abs(gray[y*w+(x+1)]-gray[y*w+(x-1)]);
const gy=Math.abs(gray[(y+1)*w+x]-gray[(y-1)*w+x]);
score+=gx+gy;
}
rowScore[y]=cnt>0 ? score/cnt+cnt*0.08 : 0;
}
const smoothed=new Float32Array(h); let maxVal=1e-6;
for (let y=0;y<h;y++) {
let s=0,c=0;
for (let dy=-2;dy<=2;dy++) {
const ny=y+dy; if(ny<0||ny>=h)continue;
const wt=3-Math.abs(dy); s+=rowScore[ny]*wt; c+=wt;
}
smoothed[y]=s/c;
if (smoothed[y]>maxVal) maxVal=smoothed[y];
}
let segs=[],y=0;
while (y<h) {
const t=smoothed[y]/maxVal;
const step=Math.max(1,Math.round(maxStep*Math.pow(1-t,0.45)+0.5));
let run=false,sx=0;
for (let x=0;x<w;x++) {
const i=y*w+x; let draw=false;
if (!bgMask[i]) {
const lum=gray[i];
if (lum<70) draw=true;
else {
const ri=i*4;
if (!(skipSkin&&isSkinPixel(rgba[ri],rgba[ri+1],rgba[ri+2]))&&lum<185) draw=true;
}
}
if (draw&&!run){run=true;sx=x;}
else if (!draw&&run) {
run=false;
const seg={x1:sx,y1:y,x2:x,y2:y};
if (colorMode) seg.color=sampleColorNeighborhood(sx,x,y,rgba,w);
segs.push(seg);
}
}
if (run) { const seg={x1:sx,y1:y,x2:w-1,y2:y}; if(colorMode)seg.color=sampleColorNeighborhood(sx,w-1,y,rgba,w); segs.push(seg); }
y += smoothed[y]/maxVal>0.6 ? 1 : step;
}
return mergeCollinearSegments(segs,25,6);
}
// --- QUAD modu ---
function getAvgColorAndVariance(rgba, gray, x, y, w, h, rw) {
let r=0,g=0,b=0,gs=0,gsq=0,cnt=0;
for (let dy=y;dy<y+h;dy++) for (let dx=x;dx<x+w;dx++) {
const i=(dy*rw+dx)*4;
r+=rgba[i]; g+=rgba[i+1]; b+=rgba[i+2];
const v=gray[dy*rw+dx]; gs+=v; gsq+=v*v; cnt++;
}
if (!cnt) return {var:99999,color:'rgb(0,0,0)'};
const mean=gs/cnt;
return { var:(gsq/cnt)-mean*mean, color:`rgb(${Math.round(r/cnt)},${Math.round(g/cnt)},${Math.round(b/cnt)})` };
}
function quadtreeSegments(rgba, gray, x, y, w, h, rw, maxDepth, varThr, depth=0) {
const segs=[], info=getAvgColorAndVariance(rgba,gray,x,y,w,h,rw);
if (depth>=maxDepth||w<8||h<8||info.var<varThr) {
if (w>1&&h>1) segs.push({x1:x,y1:y,x2:x+w,y2:y+h,color:info.color,tool:'op2'});
return segs;
}
const hw=Math.floor(w/2),hh=Math.floor(h/2),rw2=w-hw,rh2=h-hh;
segs.push(...quadtreeSegments(rgba,gray,x,y,hw,hh,rw,maxDepth,varThr,depth+1));
segs.push(...quadtreeSegments(rgba,gray,x+hw,y,rw2,hh,rw,maxDepth,varThr,depth+1));
segs.push(...quadtreeSegments(rgba,gray,x,y+hh,hw,rh2,rw,maxDepth,varThr,depth+1));
segs.push(...quadtreeSegments(rgba,gray,x+hw,y+hh,rw2,rh2,rw,maxDepth,varThr,depth+1));
return segs;
}
function processQuadMode(imgEl, maxDepth, varThr, colorMode) {
const raw=getImageData(imgEl);
const gray=toGray(raw.data,1.0);
const segs=quadtreeSegments(raw.data,gray,0,0,CW,CH,CW,maxDepth,varThr);
if (!colorMode) segs.forEach(s=>delete s.color);
return segs;
}
// --- VECTOR modu (marching squares) ---
function vectorGaussianBlur(gray, w, h, sigma) {
if (sigma<=0) return gray;
const radius=Math.ceil(sigma*3), size=radius*2+1;
const kernel=new Float32Array(size); let sum=0;
for (let i=-radius;i<=radius;i++) { const v=Math.exp(-(i*i)/(2*sigma*sigma)); kernel[i+radius]=v; sum+=v; }
for (let i=0;i<size;i++) kernel[i]/=sum;
const tmp=new Float32Array(w*h);
for (let y=0;y<h;y++) for (let x=0;x<w;x++) {
let s=0;
for (let k=-radius;k<=radius;k++) s+=gray[y*w+Math.min(Math.max(x+k,0),w-1)]*kernel[k+radius];
tmp[y*w+x]=s;
}
const out=new Float32Array(w*h);
for (let y=0;y<h;y++) for (let x=0;x<w;x++) {
let s=0;
for (let k=-radius;k<=radius;k++) s+=tmp[Math.min(Math.max(y+k,0),h-1)*w+x]*kernel[k+radius];
out[y*w+x]=s;
}
return out;
}
function vectorImageToGrayscale(imageData) {
const data=imageData.data, gray=new Float32Array(CW*CH);
for (let i=0;i<CW*CH;i++) {
const idx=i*4;
if (data[idx+3]<128){gray[i]=255;continue;}
gray[i]=0.299*data[idx]+0.587*data[idx+1]+0.114*data[idx+2];
}
return gray;
}
function marchingSquares(blurred, w, h, threshold, cellSize) {
const gridW=Math.floor(w/cellSize)+1, gridH=Math.floor(h/cellSize)+1;
const grid=new Float32Array(gridW*gridH);
for (let y=0;y<gridH;y++) for (let x=0;x<gridW;x++)
grid[y*gridW+x]=blurred[Math.min(y*cellSize,h-1)*w+Math.min(x*cellSize,w-1)];
const edges=[];
function interp(v1,v2,faceLen,bx,by,horiz) {
if (Math.abs(v2-v1)<1e-6) return {x:bx,y:by};
const t=(threshold-v1)/(v2-v1);
return horiz?{x:bx+t*faceLen,y:by}:{x:bx,y:by+t*faceLen};
}
for (let j=0;j<gridH-1;j++) for (let i=0;i<gridW-1;i++) {
const x0=i*cellSize, y0=j*cellSize;
const v0=grid[j*gridW+i], v1=grid[j*gridW+(i+1)];
const v2=grid[(j+1)*gridW+(i+1)], v3=grid[(j+1)*gridW+i];
const b0=v0<threshold, b1=v1<threshold, b2=v2<threshold, b3=v3<threshold;
const idx=(b0?1:0)|(b1?2:0)|(b2?4:0)|(b3?8:0);
if (idx===0||idx===15) continue;
let top,right,bot,left;
if (b0!==b1) top =interp(v0,v1,cellSize,x0,y0,true);
if (b1!==b2) right=interp(v1,v2,cellSize,x0+cellSize,y0,false);
if (b2!==b3) bot =interp(v3,v2,cellSize,x0,y0+cellSize,true);
if (b3!==b0) left =interp(v0,v3,cellSize,x0,y0,false);
switch(idx){
case 1:edges.push([top,left]);break; case 2:edges.push([top,right]);break;
case 3:edges.push([right,left]);break; case 4:edges.push([right,bot]);break;
case 5:edges.push([top,left]);edges.push([bot,right]);break;
case 6:edges.push([top,bot]);break; case 7:edges.push([bot,left]);break;
case 8:edges.push([left,bot]);break; case 9:edges.push([top,bot]);break;
case 10:edges.push([top,right]);edges.push([bot,left]);break;
case 11:edges.push([right,bot]);break; case 12:edges.push([right,left]);break;
case 13:edges.push([top,right]);break; case 14:edges.push([top,left]);break;
}
}
return edges;
}
function buildVectorPaths(edges) {
const key=pt=>`${pt.x.toFixed(2)},${pt.y.toFixed(2)}`;
const ptToEdges=new Map();
edges.forEach(([p1,p2],idx)=>{
[p1,p2].forEach(p=>{ const k=key(p); if(!ptToEdges.has(k))ptToEdges.set(k,[]); ptToEdges.get(k).push(idx); });
});
const visited=new Set(), paths=[];
for (let i=0;i<edges.length;i++) {
if (visited.has(i)) continue;
let [startP,nextP]=edges[i]; visited.add(i);
const path=[startP,nextP]; let cur=nextP, curIdx=i;
while (true) {
const conn=ptToEdges.get(key(cur))||[];
let nextIdx=-1, nextPt=null;
for (const eIdx of conn) {
if (eIdx===curIdx||visited.has(eIdx)) continue;
const [ep1,ep2]=edges[eIdx];
if (key(ep1)===key(cur)){nextPt=ep2;nextIdx=eIdx;break;}
if (key(ep2)===key(cur)){nextPt=ep1;nextIdx=eIdx;break;}
}
if (nextIdx===-1) break;
visited.add(nextIdx); path.push(nextPt); cur=nextPt; curIdx=nextIdx;
}
paths.push(path.length>2?path:[edges[i][0],edges[i][1]]);
}
paths.sort((a,b)=>a[0].y-b[0].y);
return paths;
}
function startVectorDraw(paths, speed) {
const obj=getDrawer();
if (!obj){setStatus("❌ Çizim alanı bulunamadı!",false);return;}
const {d,rect}=obj;
const rand=(min,max)=>min+Math.random()*(max-min);
const events=[]; let t=0;
paths.forEach(path=>{
if (!path.length) return;
t+=rand(80,250);
events.push({type:"down",x:path[0].x,y:path[0].y,time:t});
for (let i=1;i<path.length;i++) {
const prev=path[i-1],cur=path[i];
const dx=cur.x-prev.x, dy=cur.y-prev.y;
let dt=Math.max(18,(Math.sqrt(dx*dx+dy*dy)/speed)*1000)+rand(-12,12);
if (Math.sqrt(dx*dx+dy*dy)<2) dt=Math.max(dt,35);
t+=dt;
events.push({type:"move",x:cur.x,y:cur.y,time:t});
}
const last=path[path.length-1];
events.push({type:"up",x:last.x,y:last.y,time:t});
t+=rand(150,400);
});
if (!events.length){setStatus("⚠ Çizilecek kontur bulunamadı",false);return;}
const start=performance.now(); let idx=0; vectorDrawCancel=false;
function dispatch(type,x,y) {
const mk={clientX:rect.left+x,clientY:rect.top+y,preventDefault(){}};
if(type==="down")d._eventMouseDown(mk);
else if(type==="move")d._eventMouseMove(mk);
else d._eventMouseUp(mk);
}
function frame() {
if (vectorDrawCancel){setStatus("⛔ Çizim durduruldu",false);return;}
const now=performance.now()-start;
while (idx<events.length&&events[idx].time<=now) { dispatch(events[idx].type,events[idx].x,events[idx].y); idx++; }
if (idx>=events.length){setStatus(`✅ Bitti! (${paths.length} kontur)`,false);vectorAnimationId=null;return;}
setStatus(`✏ Çiziliyor… ${idx}/${events.length}`,true);
vectorAnimationId=requestAnimationFrame(frame);
}
vectorAnimationId=requestAnimationFrame(frame);
setStatus(`▶ Başlıyor (${paths.length} kontur)`,true);
}
function stopVectorDrawing() {
vectorDrawCancel=true;
if (vectorAnimationId){cancelAnimationFrame(vectorAnimationId);vectorAnimationId=null;}
}
function processVectorMode(imgEl, threshold, blurSigma, cellSize, speed) {
const cv=document.createElement("canvas"); cv.width=CW; cv.height=CH;
const ctx=cv.getContext("2d");
const scale=Math.min(CW/imgEl.naturalWidth,CH/imgEl.naturalHeight);
const w=imgEl.naturalWidth*scale, h=imgEl.naturalHeight*scale;
ctx.fillStyle="#fff"; ctx.fillRect(0,0,CW,CH);
ctx.drawImage(imgEl,(CW-w)/2,(CH-h)/2,w,h);
const imageData=ctx.getImageData(0,0,CW,CH);
const gray=vectorImageToGrayscale(imageData);
const blurred=vectorGaussianBlur(gray,CW,CH,blurSigma);
const edges=marchingSquares(blurred,CW,CH,threshold,cellSize);
if (!edges.length){setStatus("⚠ Kontur bulunamadı — ayarları değiştirin",false);return null;}
return {paths:buildVectorPaths(edges),speed};
}
// --- ana pipeline ---
function processImage(imgEl, mode, threshold, contrast, step, colorMode, skipSkin, maxDepth, varThr) {
if (mode==='quad') return processQuadMode(imgEl,maxDepth,varThr,colorMode);
if (mode==='vector') return null;
if (mode==='classic') {
const processed=processImageClassic(imgEl,threshold,contrast);
const baseColor=colorMode?hexToRgb(document.getElementById("colorRangeInput").value):null;
return collectImageSegmentsClassic(processed,step,colorMode,baseColor);
}
const raw=getImageData(imgEl);
const gray=toGray(raw.data,contrast);
const blurred=gaussianBlur(gray,CW,CH);
const {bg}=buildBackgroundMaskAuto(blurred,CW,CH);
return collectPortraitFill(blurred,bg,raw.data,CW,CH,step,skipSkin,colorMode);
}
// --- çizim başlatıcı ---
function startDraw(segs, startFrom=0) {
const obj=getDrawer();
if (!obj){setStatus("❌ Çizim alanı yok!",false);return;}
if (drawing&&startFrom===0){setStatus("⏳ Zaten çiziyor!",false);return;}
if (!segs.length){setStatus("⚠️ Çizilecek piksel yok",false);return;}
const {d,rect}=obj;
drawing=true; window.__garticBotStop=false; timeoutIds.clear();
globalSegs=segs; globalColorMode=false; lastIndex=startFrom;
setStatus(`⏳ ${startFrom} / ${segs.length}`,true);
let delay=0;
for (let i=startFrom;i<segs.length;i++) {
if (window.__garticBotStop) break;
const s=segs[i];
sendLine(d,rect,s.x1,s.y1,s.x2,s.y2,delay,s.tool);
delay+=Math.max(18,CFG.delay+Math.random()*5-2);
const id=setTimeout(()=>{
if (window.__garticBotStop) return;
lastIndex=i+1;
setStatus(`⏳ ${i+1} / ${segs.length}`,true);
if (i+1>=segs.length){drawing=false;lastIndex=0;setStatus(`✅ Bitti! (${segs.length} çizgi)`,false);document.getElementById('gu-resume').style.display='none';}
},delay);
timeoutIds.add(id);
}
}
function startDrawColored(segs, startFrom=0) {
const obj=getDrawer();
if (!obj){setStatus("❌ Çizim alanı yok!",false);return;}
if (drawing&&startFrom===0){setStatus("⏳ Zaten çiziyor!",false);return;}
if (!segs.length){setStatus("⚠️ Çizilecek piksel yok",false);return;}
const {d,rect}=obj;
drawing=true; window.__garticBotStop=false; timeoutIds.clear();
globalSegs=segs; globalColorMode=true; lastIndex=startFrom;
const total=segs.length; let done=startFrom, gDelay=0;
setStatus(`⏳ ${done} / ${total} (Renkli)`,true);
for (let i=startFrom;i<segs.length;i++) {
if (window.__garticBotStop) break;
const s=segs[i];
if (s.color){
const cd=gDelay;
timeoutIds.add(setTimeout(()=>{ if(!window.__garticBotStop)setGarticColor(s.color); },cd));
gDelay+=30;
}
sendLine(d,rect,s.x1,s.y1,s.x2,s.y2,gDelay,s.tool);
gDelay+=Math.max(18,CFG.delay+Math.random()*5-2);
timeoutIds.add(setTimeout(()=>{
if (window.__garticBotStop) return;
done++; lastIndex=done;
setStatus(`⏳ ${done} / ${total} (Renkli)`,true);
if (done>=total){drawing=false;lastIndex=0;setStatus(`✅ Bitti! ${total} blok (Renkli)`,false);document.getElementById('gu-resume').style.display='none';}
},gDelay));
}
}
function setGarticColor(color) {
const el=document.getElementById("colorRangeInput");
if (el){el.value=color;el.dispatchEvent(new Event('input',{bubbles:true}));}
}
function drawImage(imgEl, mode, threshold, contrast, step, colorMode, skipSkin, maxDepth, varThr, vectorSettings) {
setStatus("⚙️ Hesaplanıyor...",true);
setTimeout(()=>{
if (mode==='vector') {
const result=processVectorMode(imgEl,vectorSettings.threshold,vectorSettings.blur,vectorSettings.cellSize,vectorSettings.speed);
if (result) startVectorDraw(result.paths,result.speed);
return;
}
const segs=processImage(imgEl,mode,threshold,contrast,step,colorMode,skipSkin,maxDepth,varThr);
setStatus(`📊 ${segs.length} segment hazır`,true);
setTimeout(()=>{ if(colorMode)startDrawColored(segs); else startDraw(segs); },300);
},50);
}
/* ─────────────────────────── UI ─────────────────────────── */
function setStatus(msg, busy) {
const el=document.getElementById("gu-status");
const bim=document.getElementById("gu-draw-img");
const stp=document.getElementById("gu-stop");
if (el){el.textContent=msg;el.style.color=busy?"#f4c06a":"#4dffd6";}
if (bim){bim.disabled=busy;bim.style.opacity=busy?"0.4":"1";}
if (stp) stp.style.display=busy?"block":"none";
}
function makeDraggable(panel) {
let sx,sy,sl,st,drag=false;
function onStart(ex,ey){sx=ex;sy=ey;const r=panel.getBoundingClientRect();sl=r.left;st=r.top;drag=true;}
function onMove(ex,ey){if(!drag)return;panel.style.left=Math.max(0,sl+ex-sx)+"px";panel.style.top=Math.max(0,st+ey-sy)+"px";panel.style.right="auto";panel.style.bottom="auto";}
function onEnd(){drag=false;}
const hdr=document.getElementById("gu-handle");
hdr.addEventListener("mousedown",e=>{onStart(e.clientX,e.clientY);e.preventDefault();});
document.addEventListener("mousemove",e=>onMove(e.clientX,e.clientY));
document.addEventListener("mouseup",onEnd);
hdr.addEventListener("touchstart",e=>{const t=e.touches[0];onStart(t.clientX,t.clientY);},{passive:true});
document.addEventListener("touchmove",e=>{if(!drag)return;const t=e.touches[0];onMove(t.clientX,t.clientY);},{passive:true});
document.addEventListener("touchend",onEnd);
}
function toggleMinimize() {
const body=document.getElementById("gu-body");
const btn=document.getElementById("gu-min-btn");
const c=body.style.display==="none";
body.style.display=c?"block":"none";
if(btn)btn.textContent=c?"▾":"▸";
}
/* ─── YENİ TEMA ─────────────────────────────────────────── */
const CSS = `
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap');
#gu-panel {
position: fixed; left: 14px; bottom: 14px;
width: min(300px, calc(100vw - 28px));
background: #0e0e14;
border: 1px solid rgba(255,255,255,0.06);
border-radius: 18px;
z-index: 999999;
font-family: 'Inter', 'Segoe UI', sans-serif;
box-shadow: 0 0 0 1px rgba(255,255,255,0.03), 0 24px 60px rgba(0,0,0,0.7);
color: #c9c4e8;
user-select: none; touch-action: none;
max-height: 94vh; overflow: hidden;
display: flex; flex-direction: column;
}
#gu-panel * { box-sizing: border-box; }
#gu-handle {
display: flex; align-items: center; justify-content: space-between;
padding: 12px 14px 10px;
cursor: grab;
background: linear-gradient(135deg, #13111e 0%, #0e0e14 100%);
border-radius: 18px 18px 0 0;
border-bottom: 1px solid rgba(255,255,255,0.05);
flex-shrink: 0;
}
#gu-handle:active { cursor: grabbing; }
.gu-handle-title {
font-size: 11px; font-weight: 800; letter-spacing: 2px;
color: #7c5fe6; text-transform: uppercase;
}
.gu-handle-badge {
font-size: 8px; color: rgba(124,95,230,0.45);
letter-spacing: 1px; margin-top: 1px;
}
#gu-min-btn {
background: rgba(124,95,230,0.12);
border: 1px solid rgba(124,95,230,0.2);
color: #7c5fe6; font-size: 12px;
cursor: pointer; padding: 2px 7px; line-height: 1;
border-radius: 6px;
-webkit-tap-highlight-color: transparent;
transition: background .15s;
}
#gu-min-btn:hover { background: rgba(124,95,230,0.22); }
#gu-body {
overflow-y: auto; padding: 12px 12px 12px;
max-height: calc(94vh - 54px);
scrollbar-width: thin;
scrollbar-color: rgba(124,95,230,0.25) transparent;
}
#gu-body::-webkit-scrollbar { width: 3px; }
#gu-body::-webkit-scrollbar-thumb { background: rgba(124,95,230,0.25); border-radius: 99px; }
.gu-label {
font-size: 9px; color: rgba(124,95,230,0.55);
margin-bottom: 4px; letter-spacing: 1.2px; text-transform: uppercase; font-weight: 600;
}
.gu-input, .gu-range, .gu-select {
width: 100%;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.07);
color: #c9c4e8; padding: 8px 10px;
border-radius: 10px; font-size: 12px; outline: none;
margin-bottom: 8px; transition: border-color .2s;
font-family: inherit; -webkit-appearance: none;
}
.gu-input:focus, .gu-select:focus {
border-color: rgba(124,95,230,0.5);
background: rgba(124,95,230,0.06);
}
.gu-range {
padding: 4px 0; cursor: pointer; height: 24px;
accent-color: #7c5fe6; border: none; background: transparent;
}
.gu-select {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' fill='%237c5fe6'%3E%3Cpath d='M5 7L1 3h8z'/%3E%3C/svg%3E");
background-repeat: no-repeat; background-position: right 10px center; padding-right: 26px;
}
.gu-btn {
width: 100%; border: none; padding: 10px 12px;
border-radius: 11px; cursor: pointer;
font-weight: 700; font-size: 12px; letter-spacing: 1px;
transition: transform .1s, opacity .15s, box-shadow .15s;
text-transform: uppercase; margin-bottom: 6px;
min-height: 42px; font-family: inherit;
-webkit-tap-highlight-color: transparent; touch-action: manipulation;
}
.gu-btn:hover:not(:disabled) { transform: translateY(-1px); }
.gu-btn:active:not(:disabled) { transform: scale(0.97); }
.gu-btn:disabled { opacity: 0.4; cursor: not-allowed; }
#gu-draw-img {
background: linear-gradient(135deg, #5b38c8, #8b5cf6);
color: #fff;
box-shadow: 0 4px 18px rgba(124,95,230,0.3);
}
#gu-draw-img:hover:not(:disabled) {
box-shadow: 0 6px 24px rgba(124,95,230,0.45);
}
#gu-stop {
background: linear-gradient(135deg, #8b1c1c, #c0392b);
color: #fff; display: none;
}
#gu-resume {
background: linear-gradient(135deg, #1a6b3a, #27ae60);
color: #fff; display: none;
}
#gu-clear {
background: rgba(255,255,255,0.04);
color: rgba(201,196,232,0.45);
border: 1px solid rgba(255,255,255,0.07);
font-size: 11px;
}
#gu-clear:hover { color: rgba(201,196,232,0.7); }
#gu-status {
font-size: 10px; color: #4dffd6;
text-align: center; min-height: 14px;
letter-spacing: .5px; margin-bottom: 6px;
font-weight: 600;
}
#gu-img-preview {
width: 100%; height: 76px; object-fit: contain;
background: rgba(255,255,255,0.03);
border: 1px solid rgba(255,255,255,0.07);
border-radius: 10px; margin-bottom: 8px; display: none;
}
#gu-img-drop {
width: 100%; height: 60px;
border: 1.5px dashed rgba(124,95,230,0.25);
border-radius: 12px; display: flex;
align-items: center; justify-content: center;
font-size: 11px; color: rgba(124,95,230,0.45);
cursor: pointer; margin-bottom: 8px;
transition: border-color .2s, color .2s, background .2s;
background: rgba(124,95,230,0.04);
min-height: 42px; font-weight: 600;
-webkit-tap-highlight-color: transparent;
}
#gu-img-drop:hover, #gu-img-drop:active {
border-color: rgba(124,95,230,0.6);
color: #a88bff;
background: rgba(124,95,230,0.1);
}
#gu-img-drop.drag {
border-color: #a88bff; color: #c4a8ff;
background: rgba(124,95,230,0.14);
}
#gu-img-input { display: none; }
#gu-color-picker {
display: flex; align-items: center; gap: 10px; margin-bottom: 8px;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.07);
border-radius: 10px; padding: 8px 10px;
}
#colorRangeInput {
width: 40px; height: 32px; border: none;
border-radius: 6px; cursor: pointer; background: none; flex-shrink: 0;
}
.gu-color-label { font-size: 11px; color: #a88bff; font-weight: 700; letter-spacing: .5px; flex: 1; }
.gu-slider-row { margin-bottom: 8px; }
.gu-slider-label {
display: flex; justify-content: space-between; margin-bottom: 2px;
}
.gu-slider-label span:first-child { font-size: 9px; color: rgba(124,95,230,0.5); text-transform: uppercase; letter-spacing: 1px; font-weight: 600; }
.gu-slider-label span:last-child { font-size: 10px; color: #9d86e8; font-weight: 700; }
.gu-toggle-row {
display: flex; align-items: center; gap: 10px; margin-bottom: 8px;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.07);
border-radius: 10px; padding: 10px; cursor: pointer; min-height: 42px;
-webkit-tap-highlight-color: transparent;
transition: background .15s;
}
.gu-toggle-row:hover { background: rgba(124,95,230,0.08); }
.gu-toggle-row input[type="checkbox"] { accent-color: #7c5fe6; width: 16px; height: 16px; cursor: pointer; flex-shrink: 0; }
.gu-toggle-row span { font-size: 11px; color: #a88bff; font-weight: 700; letter-spacing: .4px; }
.gu-skin-box {
background: rgba(255,255,255,0.03);
border: 1px solid rgba(255,255,255,0.06);
border-radius: 10px; padding: 10px; margin-bottom: 8px;
}
.gu-skin-box label { display: flex; align-items: center; gap: 8px; cursor: pointer; min-height: 30px; }
.gu-skin-box input[type="checkbox"] { accent-color: #e87cad; width: 16px; height: 16px; flex-shrink: 0; }
.gu-skin-box span { font-size: 11px; color: #e8a8cc; font-weight: 700; letter-spacing: .4px; }
.gu-skin-box p { font-size: 9px; color: rgba(232,168,204,0.4); margin: 4px 0 0; line-height: 1.5; }
.gu-delay-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.gu-delay-row .gu-label { margin: 0; white-space: nowrap; }
.gu-delay-row .gu-input { margin-bottom: 0; flex: 1; }
.gu-url-row { display: flex; gap: 6px; margin-bottom: 8px; }
.gu-url-row .gu-input { margin-bottom: 0; font-size: 11px; flex: 1; }
#gu-img-url-load {
margin-bottom: 0; width: auto; padding: 8px 12px;
font-size: 12px; flex-shrink: 0; border-radius: 10px;
background: linear-gradient(135deg, #3a2080, #6344c8);
color: #e0d8ff; border: none; cursor: pointer; font-weight: 800;
min-height: 42px; min-width: 42px; font-family: inherit;
-webkit-tap-highlight-color: transparent; touch-action: manipulation;
transition: opacity .15s;
}
#gu-img-url-load:hover { opacity: .85; }
#gu-img-url-status { font-size: 9px; color: #e8906a; text-align: center; min-height: 12px; margin-bottom: 6px; }
#gu-img-remove {
margin-bottom: 8px; background: rgba(255,255,255,0.04);
color: rgba(201,80,80,0.7);
border: 1px solid rgba(255,80,80,0.12);
font-size: 11px; padding: 8px; width: 100%;
border-radius: 10px; font-family: inherit;
}
#gu-img-remove:hover { color: #ff6b6b; border-color: rgba(255,80,80,0.25); }
.gu-divider { border: none; border-top: 1px solid rgba(255,255,255,0.05); margin: 10px 0; }
.gu-mode-info {
font-size: 9px; color: rgba(124,95,230,0.45); line-height: 1.55;
margin-bottom: 8px; padding: 7px 9px;
background: rgba(124,95,230,0.05);
border-radius: 8px; border: 1px solid rgba(124,95,230,0.1);
}
.gu-tag {
display: inline-block; font-size: 8px; font-weight: 700;
padding: 2px 7px; border-radius: 99px; letter-spacing: 1px;
text-transform: uppercase; margin-bottom: 8px;
background: rgba(124,95,230,0.12);
border: 1px solid rgba(124,95,230,0.2);
color: rgba(124,95,230,0.7);
}
`;
const styleTag = document.createElement("style");
styleTag.textContent = CSS;
document.head.appendChild(styleTag);
const panel = document.createElement("div");
panel.id = "gu-panel";
panel.innerHTML = `
<div id="gu-handle">
<div>
<div class="gu-handle-title">✦ gartic çizim botu</div>
<div class="gu-handle-badge">made by zerask</div>
</div>
<button id="gu-min-btn">▾</button>
</div>
<div id="gu-body">
<div id="gu-img-drop">📂 tıkla veya sürükle</div>
<input type="file" id="gu-img-input" accept="image/*">
<img id="gu-img-preview">
<div id="gu-img-actions" style="display:none;">
<button class="gu-btn" id="gu-img-remove">✕ Resmi Kaldır</button>
</div>
<div class="gu-label">URL ile yükle</div>
<div class="gu-url-row">
<input class="gu-input" id="gu-img-url" placeholder="https://..." type="url" inputmode="url">
<button id="gu-img-url-load">↵</button>
</div>
<div id="gu-img-url-status"></div>
<hr class="gu-divider">
<div class="gu-label">Çizim Modu</div>
<select id="gu-mode" class="gu-select">
<option value="classic" selected>⬛Satır Dolgu</option>
<option value="fast">⚡Adaptif Dolgu</option>
<option value="quad">🟦Dikdörtgen Blok Portre</option>
<option value="vector">🎯Kontur Çizgi</option>
</select>
<div class="gu-mode-info" id="gu-mode-desc">
Klasik satır tabanlı dolgu.
</div>
<div id="gu-fast-controls">
<div class="gu-slider-row" id="gu-thr-row">
<div class="gu-slider-label"><span>Eşik</span><span id="gu-thr-val">240</span></div>
<input class="gu-range" type="range" id="gu-thr" min="20" max="240" value="240">
</div>
<div class="gu-slider-row" id="gu-con-row">
<div class="gu-slider-label"><span>Kontrast</span><span id="gu-con-val">3.3</span></div>
<input class="gu-range" type="range" id="gu-con" min="0.5" max="4" step="0.1" value="3.3">
</div>
<div class="gu-slider-row" id="gu-step-row">
<div class="gu-slider-label"><span>Maks Adım (px)</span><span id="gu-img-step-val">5</span></div>
<input class="gu-range" type="range" id="gu-img-step" min="2" max="20" value="5">
</div>
<div class="gu-slider-row" id="gu-quad-row" style="display:none;">
<div class="gu-slider-label"><span>Quad Derinlik</span><span id="gu-quad-depth-val">6</span></div>
<input class="gu-range" type="range" id="gu-quad-depth" min="3" max="8" value="8">
<div class="gu-slider-label" style="margin-top:6px;"><span>Varyans Eşiği</span><span id="gu-quad-var-val">400</span></div>
<input class="gu-range" type="range" id="gu-quad-var" min="50" max="2000" step="50" value="200">
</div>
<div class="gu-delay-row">
<span class="gu-label">Gecikme (ms)</span>
<input class="gu-input" id="gu-img-delay" type="number" inputmode="numeric" value="120" min="40" max="3000" step="20">
</div>
<hr class="gu-divider">
<div class="gu-skin-box" id="gu-skin-section">
<label>
<input type="checkbox" id="gu-skip-skin" checked>
<span>🎭 Ten Tonu Atla</span>
</label>
<p>Ten piksellerini atlar (yalnızca Fast modda).</p>
</div>
<div class="gu-label">Renk Seçici</div>
<div id="gu-color-picker">
<input type="color" id="colorRangeInput" value="#000000">
<span class="gu-color-label">🎨 Palet</span>
</div>
<div class="gu-toggle-row">
<input type="checkbox" id="gu-color-mode" checked>
<span>🌈 Renkli Çizim</span>
</div>
</div>
<div id="gu-vector-controls" style="display:none;">
<div class="gu-slider-row">
<div class="gu-slider-label"><span>Eşik</span><span id="gu-v-thr-val">200</span></div>
<input class="gu-range" type="range" id="gu-v-thr" min="20" max="340" value="200">
</div>
<div class="gu-slider-row">
<div class="gu-slider-label"><span>Bulanıklık</span><span id="gu-v-blur-val">1.0</span></div>
<input class="gu-range" type="range" id="gu-v-blur" min="0" max="5" step="0.1" value="1.0">
</div>
<div class="gu-slider-row">
<div class="gu-slider-label"><span>Hücre Boyutu</span><span id="gu-v-cell-val">3</span></div>
<input class="gu-range" type="range" id="gu-v-cell" min="2" max="20" value="3">
</div>
<div class="gu-slider-row">
<div class="gu-slider-label"><span>Hız (px/s)</span><span id="gu-v-speed-val">2000</span></div>
<input class="gu-range" type="range" id="gu-v-speed" min="50" max="5000" value="2000">
</div>
<div class="gu-mode-info">Marching Squares ile kenar tespiti. Kenarları takip eden sürekli çizgiler üretir.</div>
</div>
<button class="gu-btn" id="gu-draw-img">▶ ÇİZ</button>
<button class="gu-btn" id="gu-stop">⛔ DURDUR</button>
<button class="gu-btn" id="gu-resume">▶ DEVAM ET</button>
<button class="gu-btn" id="gu-clear">🗑 Tuvali Temizle</button>
<div id="gu-status">hazır</div>
</div>
`;
document.body.appendChild(panel);
/* ─── event bağlantıları ─────────────────────────────────── */
makeDraggable(panel);
document.getElementById("gu-min-btn").addEventListener("click", toggleMinimize);
const modeSelect = document.getElementById("gu-mode");
const modeDesc = document.getElementById("gu-mode-desc");
const stepRow = document.getElementById("gu-step-row");
const quadRow = document.getElementById("gu-quad-row");
const thrRow = document.getElementById("gu-thr-row");
const conRow = document.getElementById("gu-con-row");
const skinSection = document.getElementById("gu-skin-section");
const fastControls = document.getElementById("gu-fast-controls");
const vecControls = document.getElementById("gu-vector-controls");
const modeInfos = {
classic: "Klasik satır tabanlı dolgu. Threshold + kontrast ayarları ile kontrol edilir.",
fast: "Adaptif adım + satır birleştirme. Hız ve kalite dengesi.",
quad: "İçi dolu dikdörtgen (op2). Piksel mükemmel bloklar.",
vector: "Marching Squares ile kenar tespiti. Kenarları takip eden sürekli çizgiler."
};
modeSelect.addEventListener("change", () => {
const m = modeSelect.value;
modeDesc.textContent = modeInfos[m] || "";
const isQuad=m==='quad', isFast=m==='fast', isVec=m==='vector', isCls=m==='classic';
fastControls.style.display = (!isVec) ? 'block' : 'none';
vecControls.style.display = isVec ? 'block' : 'none';
stepRow.style.display = (isFast||isCls) ? 'block' : 'none';
quadRow.style.display = isQuad ? 'block' : 'none';
thrRow.style.display = (isFast||isCls) ? 'block' : 'none';
conRow.style.display = (isFast||isCls) ? 'block' : 'none';
skinSection.style.display = isFast ? 'block' : 'none';
});
// slider etiketleri
const sliders = [
["gu-thr","gu-thr-val",v=>v],
["gu-con","gu-con-val",v=>parseFloat(v).toFixed(1)],
["gu-img-step","gu-img-step-val",v=>v],
["gu-quad-depth","gu-quad-depth-val",v=>v],
["gu-quad-var","gu-quad-var-val",v=>v],
["gu-v-thr","gu-v-thr-val",v=>v],
["gu-v-blur","gu-v-blur-val",v=>v],
["gu-v-cell","gu-v-cell-val",v=>v],
["gu-v-speed","gu-v-speed-val",v=>v],
];
sliders.forEach(([id,valId,fmt])=>{
document.getElementById(id).addEventListener("input",function(){ document.getElementById(valId).textContent=fmt(this.value); });
});
const imgDrop = document.getElementById("gu-img-drop");
const imgInput = document.getElementById("gu-img-input");
const imgPreview = document.getElementById("gu-img-preview");
function openFilePicker(){ imgInput.value=""; imgInput.click(); }
imgDrop.addEventListener("click", openFilePicker);
imgInput.addEventListener("change", e=>{
const f=e.target.files&&e.target.files[0];
if (f&&f.type.startsWith("image/")) loadPreview(f);
else if (f) setStatus("❌ Geçerli bir resim seçin",false);
});
imgDrop.addEventListener("dragover",e=>{e.preventDefault();e.stopPropagation();imgDrop.classList.add("drag");});
imgDrop.addEventListener("dragleave",e=>{e.preventDefault();e.stopPropagation();imgDrop.classList.remove("drag");});
imgDrop.addEventListener("drop",e=>{
e.preventDefault();e.stopPropagation();imgDrop.classList.remove("drag");
const f=e.dataTransfer.files&&e.dataTransfer.files[0];
if (f&&f.type.startsWith("image/")) loadPreview(f);
else if (f) setStatus("❌ Sadece resim desteklenir",false);
});
document.addEventListener("paste",e=>{
if (!e.clipboardData) return;
const items=e.clipboardData.items||[];
for (let i=0;i<items.length;i++) {
if (items[i].type.startsWith("image/")){const f=items[i].getAsFile();if(f){loadPreview(f);break;}}
}
});
let loadedImg = null;
function loadPreview(file) {
setStatus("⏳ Yükleniyor...",true);
const reader=new FileReader();
reader.onerror=()=>setStatus("❌ Okunamadı",false);
reader.onload=ev=>{
const img=new Image();
img.onload=()=>{
loadedImg=img; imgPreview.src=ev.target.result;
imgPreview.style.display="block"; imgDrop.style.display="none";
document.getElementById("gu-img-actions").style.display="block";
document.getElementById("gu-img-url-status").textContent="";
setStatus(`📷 ${(file.name||'resim').slice(0,22)}`,false);
};
img.onerror=()=>setStatus("❌ Render hatası",false);
img.src=ev.target.result;
};
reader.readAsDataURL(file);
}
function loadPreviewFromUrl(url) {
const s=document.getElementById("gu-img-url-status");
s.textContent="⏳ Yükleniyor..."; s.style.color="#f4c06a";
const img=new Image(); img.crossOrigin="anonymous";
img.onload=()=>{
loadedImg=img; imgPreview.src=url; imgPreview.style.display="block";
imgDrop.style.display="none";
document.getElementById("gu-img-actions").style.display="block";
s.textContent="✅ Yüklendi"; s.style.color="#4dffd6";
setStatus("🔗 URL'den yüklendi",false);
};
img.onerror=()=>{s.textContent="❌ CORS hatası"; s.style.color="#ff6b6b";};
img.src=url;
}
document.getElementById("gu-img-remove").addEventListener("click",()=>{
loadedImg=null; imgPreview.src=""; imgPreview.style.display="none";
imgDrop.style.display="flex";
document.getElementById("gu-img-actions").style.display="none";
document.getElementById("gu-img-url").value="";
document.getElementById("gu-img-url-status").textContent="";
imgInput.value="";
setStatus("resim kaldırıldı",false);
});
document.getElementById("gu-img-url-load").addEventListener("click",()=>{
const url=document.getElementById("gu-img-url").value.trim();
if (url) loadPreviewFromUrl(url);
});
document.getElementById("gu-img-url").addEventListener("keydown",e=>{
if (e.key==="Enter") document.getElementById("gu-img-url-load").click();
});
document.getElementById("gu-draw-img").addEventListener("click",()=>{
if (!loadedImg){setStatus("📂 önce resim yükle",false);return;}
const mode=document.getElementById("gu-mode").value;
if (mode==='vector') {
drawImage(loadedImg,mode,0,0,0,false,false,0,0,{
threshold: parseInt(document.getElementById("gu-v-thr").value),
blur: parseFloat(document.getElementById("gu-v-blur").value),
cellSize: parseInt(document.getElementById("gu-v-cell").value),
speed: parseInt(document.getElementById("gu-v-speed").value)
});
return;
}
CFG.delay = parseInt(document.getElementById("gu-img-delay").value)||120;
drawImage(
loadedImg, mode,
parseInt(document.getElementById("gu-thr").value),
parseFloat(document.getElementById("gu-con").value),
parseInt(document.getElementById("gu-img-step").value),
document.getElementById("gu-color-mode").checked,
document.getElementById("gu-skip-skin").checked,
parseInt(document.getElementById("gu-quad-depth").value),
parseInt(document.getElementById("gu-quad-var").value),
null
);
});
document.getElementById("gu-stop").addEventListener("click", stopDrawing);
document.getElementById("gu-resume").addEventListener("click", resumeDrawing);
document.getElementById("gu-clear").addEventListener("click", clearCanvas);
})();