Greasy Fork is available in English.
Gartic.io draw / Draw in Gartic / draw / Gartic auto script
// ==UserScript==
// @name Gartic Drawer
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Gartic.io draw / Draw in Gartic / draw / Gartic auto script
// @author F̷a̷r̷e̷s̷0
// @match https://gartic.io/*
// @grant none
// @license MIT
// @icon https://i.ibb.co/7d9y9T03/1777940315338.png
// @locale ar
// ==/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;
// --- الوظائف المساعدة ---
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("🗑تم التنظيف", false);
} else if (attempts >= 25) {
clearInterval(interval);
}
}, 120);
}
function clearCanvas() {
const btn = document.querySelector("li#clean");
if (btn) { btn.click(); clickYesOnDialog(); }
else setStatus("❌ مفيش حاجه ", false);
}
// --- يشتغل / يقف---
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(`⛔ متوقف (${lastIndex}/${globalSegs.length})`, false);
}
function stopDrawing() { hardStop(); stopVectorDrawing(); }
function resumeDrawing() {
if (!globalSegs.length || lastIndex >= globalSegs.length) {
setStatus("⚠️ مفيش صورة عشان تكمل الرسم", 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);
}
// --- أدوات معالجه الصورة ---
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)})`;
}
// --- دمج الاسطر ---
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;
}
// ---وضع السرعه ---
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);
}
// --- الوضع الرباعي ---
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;
}
// --- وضع المتجهات ---
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("❌ مفيش منطقة رسم!",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("⚠ مش لافي محيط مساحه",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("⛔ وقف الرسم",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(`✅ تم! (${paths.length} kontur)`,false);vectorAnimationId=null;return;}
setStatus(`✏ drawing… ${idx}/${events.length}`,true);
vectorAnimationId=requestAnimationFrame(frame);
}
vectorAnimationId=requestAnimationFrame(frame);
setStatus(`▶ starting (${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("⚠ مفيش محيط -غير الاعدادات ",false);return null;}
return {paths:buildVectorPaths(edges),speed};
}
// --- خط الانابيب ---
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);
}
// --- الوضع الكلاسيكي ---
function startDraw(segs, startFrom=0) {
const obj=getDrawer();
if (!obj){setStatus("❌ مفيش مساحه رسم متاحه!",false);return;}
if (drawing&&startFrom===0){setStatus("⏳ بيرسم!",false);return;}
if (!segs.length){setStatus("⚠️ مفيش وحدات بكسل للرسم",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(`✅تم ! (${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("❌ مفيش مساحه رسم متاحه!",false);return;}
if (drawing&&startFrom===0){setStatus("⏳ بيرسم!",false);return;}
if (!segs.length){setStatus("⚠️ مفيش وحدات بكسل للرسم",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} ( بيرسم )`,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(`✅ تم! ${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("⚙️ ثواني بيحمل...",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} الشريحه جاهزه`,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?"▾":"▸";
}
//* ───RGB ال ───────────────────────────────────────────
const CSS = `
@import url('https://fonts.googleapis.com/css2?family=Cairo:wght@900&display=swap');
/* أنيميشن قوس قزح منوع (7 ألوان) للحدود */
@keyframes rainbow-border {
0% { border-color: #ff0000; box-shadow: 0 0 25px #ff0000; } /* أحمر */
15% { border-color: #ff8000; box-shadow: 0 0 25px #ff8000; } /* برتقالي */
30% { border-color: #ffff00; box-shadow: 0 0 25px #ffff00; } /* أصفر */
45% { border-color: #00ff00; box-shadow: 0 0 25px #00ff00; } /* أخضر */
60% { border-color: #00ffff; box-shadow: 0 0 25px #00ffff; } /* أزرق فاتح */
75% { border-color: #0000ff; box-shadow: 0 0 25px #0000ff; } /* أزرق */
90% { border-color: #8b00ff; box-shadow: 0 0 25px #8b00ff; } /* بنفسجي */
100% { border-color: #ff0000; box-shadow: 0 0 25px #ff0000; }
}
/* أنيميشن النص الـ RGB */
@keyframes rainbow-text {
0% { color: #ff0000; } 15% { color: #ff8000; } 30% { color: #ffff00; }
45% { color: #00ff00; } 60% { color: #00ffff; } 75% { color: #0000ff; }
90% { color: #8b00ff; } 100% { color: #ff0000; }
}
#gu-panel {
position: fixed; right: 20px; bottom: 20px;
width: 350px; /* كبرنا العرض */
background: #000000; /* خلفية سوداء فخمة */
border: 6px solid #ff0000; border-radius: 35px; /* حدود ضخمة وكرتونية */
z-index: 999999; font-family: 'Cairo', sans-serif;
color: #fff; direction: rtl;
animation: rainbow-border 5s infinite linear;
box-shadow: 0 15px 50px rgba(0,0,0,1);
display: flex; flex-direction: column;
overflow: hidden;
}
#gu-handle {
padding: 20px; background: rgba(255,255,255,0.05);
text-align: center; cursor: move; border-bottom: 2px solid rgba(255,255,255,0.1);
}
.main-title {
font-size: 40px; /* حجم كبير جداً */
font-weight: 900;
animation: rainbow-text 3s infinite linear;
text-shadow: 5px 5px #000, -2px -2px 0 #000, 2px -2px 0 #000, -2px 2px 0 #000, 2px 2px 0 #000; /* Stroke كرتوني */
margin: 0;
letter-spacing: -1px;
}
#gu-body { padding: 20px; max-height: 75vh; overflow-y: auto; scrollbar-width: none; }
#gu-body::-webkit-scrollbar { display: none; }
.gu-label { font-size: 15px; color: #ffff00; font-weight: bold; display: block; margin-bottom: 8px; }
.gu-input, .gu-select {
width: 100%; padding: 12px; background: #111;
border: 3px solid #333; border-radius: 15px;
color: #00ff00; font-family: 'Cairo'; margin-bottom: 15px; font-weight: bold;
}
/* الزراير الخضراء الكرتونية */
.gu-btn {
width: 100%; padding: 16px; margin-top: 12px;
border: none; border-radius: 20px; font-weight: 900;
font-size: 20px; cursor: pointer;
transition: 0.2s; text-transform: uppercase;
box-shadow: 0 6px 0 #006600; /* ظل كرتوني */
}
#gu-draw-img {
background: #00ff00; color: #000;
}
#gu-draw-img:hover { background: #00cc00; transform: translateY(-2px); box-shadow: 0 8px 0 #004400; }
#gu-draw-img:active { transform: translateY(4px); box-shadow: 0 2px 0 #004400; }
#gu-stop { background: #ff0000; color: #fff; box-shadow: 0 6px 0 #880000; }
#gu-resume { background: #0088ff; color: #fff; box-shadow: 0 6px 0 #004488; }
#gu-clear { background: #444; color: #fff; font-size: 14px; box-shadow: 0 4px 0 #222; }
.url-row { display: flex; gap: 8px; margin-bottom: 15px; }
#gu-img-url-load { background: #00ff00; border: none; border-radius: 12px; width: 60px; color: #000; font-weight: 900; cursor: pointer; }
.skin-box {
background: #111; padding: 15px;
border-radius: 20px; margin-bottom: 15px; border: 2px solid #222;
}
.fares-credit {
font-size: 12px; color: #555; text-align: left;
padding: 10px 20px; font-weight: bold; letter-spacing: 2px;
animation: rainbow-text 10s infinite linear;
}
`;
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 id="gu-min-btn" style="position: absolute; left: 20px; top: 20px; cursor: pointer; font-size: 20px;">▾</div>
<h1 class="main-title">Gartic Drawer</h1>
<div style="font-size: 12px; color: #fff; font-weight: bold; letter-spacing: 3px; opacity: 0.5;"> CREATED BY F̷a̷r̷e̷s̷</div>
</div>
<div id="gu-drop-zone" style="border: 4px dashed #333; padding: 25px; text-align: center; border-radius: 20px; cursor: pointer; color: #555; margin-bottom: 15px; font-weight: bold; transition: 0.3s;">
📸 ارفع صورتك هنا
<div style="font-size: 10px; color: #444; margin-top: 5px;">(أو اسحبها وافلتها هنا)</div>
</div>
<input type="file" id="gu-file-input" style="display:none" accept="image/*">
<label class="gu-label">رابط الصورة</label>
<div class="url-row">
<input type="text" id="gu-img-url" class="gu-input" placeholder="https://..." style="margin-bottom:0">
<button id="gu-img-url-load">GO</button>
</div>
<label class="gu-label">طريقة الرسم</label>
<select id="gu-mode" class="gu-select">
<option value="classic">⬛ خطوط عادية</option>
<option value="fast" selected>⚡ رسم سريع جداً</option>
<option value="quad">🟦 مربعات (بكسل)</option>
<option value="vector">🎯 تحديد أطراف</option>
</select>
<div class="skin-box">
<label class="gu-label">الحساسية: <span id="gu-thr-val" style="color:#0f0">240</span></label>
<input type="range" id="gu-thr" class="gu-input" style="padding:0; accent-color:#0f0;" min="20" max="240" value="240">
<label class="gu-label">التباين: <span id="gu-con-val" style="color:#0f0">3.3</span></label>
<input type="range" id="gu-con" class="gu-input" style="padding:0; accent-color:#0f0;" min="0.5" max="4" step="0.1" value="3.3">
</div>
<label class="gu-label">سرعة الخطوة:</label>
<input type="range" id="gu-img-step" class="gu-input" style="padding:0; accent-color:#0f0;" min="2" max="20" value="5">
<label class="gu-label">التأخير (ms):</label>
<input type="number" id="gu-img-delay" class="gu-input" value="120">
<div class="skin-box">
<label style="display:flex; align-items:center; gap:10px; cursor:pointer;">
<input type="checkbox" id="gu-skip-skin" checked style="width:20px; height:20px; accent-color:#0f0;">
<span style="color: #0f0; font-weight: bold; font-size:16px;">🎭 تخطي الوجوه</span>
</label>
</div>
<div style="background: #111; padding: 15px; border-radius: 20px; border: 2px solid #222; margin-bottom: 15px;">
<label class="gu-label">اللون اليدوي</label>
<input type="color" id="colorRangeInput" value="#00ff00" style="width:100%; height:40px; border:none; background:none; cursor:pointer;">
</div>
<label style="display:flex; align-items:center; gap:10px; cursor:pointer; margin-bottom:20px; background:rgba(0,255,0,0.1); padding:10px; border-radius:15px;">
<input type="checkbox" id="gu-color-mode" checked style="width:20px; height:20px; accent-color:#0f0;">
<span style="color: #0f0; font-weight: bold; font-size:18px;">🌈 تشغيل الألوان (RGB)</span>
</label>
<button class="gu-btn" id="gu-draw-img">▶ ابـدأ الـرسم</button>
<button class="gu-btn" id="gu-stop">⛔ وقـف</button>
<button class="gu-btn" id="gu-resume">▶ كـمـل</button>
<button class="gu-btn" id="gu-clear">🗑️ امسح اللوحة</button>
<div id="gu-status" style="text-align: center; color: #fff; margin-top: 15px; font-weight:bold; text-shadow: 2px 2px #000;">النظام شغال </div>
</div>
<div class="fares-credit">Created By F̷a̷r̷e̷s̷</div>
</div>
`;
document.body.appendChild(panel);
makeDraggable(panel);
document.getElementById("gu-min-btn").addEventListener("click", toggleMinimize);
/* ─── ربط الأحداث (التصليح النهائي) ─── */
let loadedImg = null;
const fileInput = document.getElementById("gu-file-input");
const dropZone = document.getElementById("gu-drop-zone");
// وظيفة التعامل مع الملفات
const handleFile = (file) => {
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
loadedImg = img;
setStatus("🖼️ الصورة جاهزة", false);
dropZone.style.borderColor = "#00ff00";
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
};
dropZone.onclick = () => fileInput.click();
fileInput.onchange = (e) => handleFile(e.target.files[0]);
// تحميل من رابط
document.getElementById("gu-img-url-load").onclick = () => {
const url = document.getElementById("gu-img-url").value;
if (!url) return;
const img = new Image();
img.crossOrigin = "Anonymous";
img.onload = () => {
loadedImg = img;
setStatus("🖼️ تم تحميل الرابط", false);
};
img.onerror = () => setStatus("❌ فشل تحميل الرابط", false);
img.src = url;
};
// زرار الرسم الكبير
document.getElementById("gu-draw-img").onclick = () => {
if (!loadedImg) {
setStatus("📂 ارفع الصورة الأول !", false);
return;
}
const mode = document.getElementById("gu-mode").value;
CFG.delay = parseInt(document.getElementById("gu-img-delay").value) || 120;
const vectorSettings = {
threshold: parseInt(document.getElementById("gu-thr").value),
blur: 1.2,
cellSize: 4,
speed: 500
};
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,
8, // quad depth
1500, // quad var
vectorSettings
);
};
// التحكم
document.getElementById("gu-stop").onclick = stopDrawing;
document.getElementById("gu-resume").onclick = resumeDrawing;
document.getElementById("gu-clear").onclick = clearCanvas;
// تحديث أرقام السلايدرز
document.getElementById("gu-thr").oninput = (e) => document.getElementById("gu-thr-val").textContent = e.target.value;
document.getElementById("gu-con").oninput = (e) => document.getElementById("gu-con-val").textContent = e.target.value;
})(); // قفلة الـ Function الأساسية