403Webshell
Server IP : 217.154.3.148  /  Your IP : 216.73.216.90
Web Server : Apache
System : Linux blissful-wright.217-154-3-148.plesk.page 6.8.0-124-generic #124-Ubuntu SMP PREEMPT_DYNAMIC Tue May 26 13:00:45 UTC 2026 x86_64
User : gracious-boyd_hfhh1h8vo9n ( 10000)
PHP Version : 8.3.31
Disable Function : opcache_get_status
MySQL : OFF  |  cURL : ON  |  WGET : OFF  |  Perl : OFF  |  Python : OFF  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /var/www/vhosts/gracious-boyd.217-154-3-148.plesk.page/nextjs/app/api/analyze/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/vhosts/gracious-boyd.217-154-3-148.plesk.page/nextjs/app/api/analyze/rectify.ts
import Jimp from 'jimp';

type RectifyOpts = {
  enable?: boolean;
  maxRotateDeg?: number;   // harte Kappung, z.B. 15°
  maxShearDeg?: number;    // harte Kappung, z.B. 8°
  sampleStep?: number;     // Abtastraster für Gradienten
  magThresh?: number;      // Magnituden-Schwelle für Kanten
};

/**
 * Haupt-Funktion: leichte Projektivkorrektur (Rotation + X-Shear).
 * - Robust bei leichter Schiefe / geringer Perspektive
 * - Kein vollständiger Homography-Warp (bewusst "lightweight")
 */
export async function maybeRectifyPerspective(img: Jimp, opts: RectifyOpts = {}): Promise<Jimp> {
  const {
    enable = true,
    maxRotateDeg = 15,
    maxShearDeg  = 8,
    sampleStep   = 3,
    magThresh    = 18,
  } = opts;

  if (!enable) return img;

  // Für Winkel-Schätzung in kleiner Auflösung arbeiten (Performance)
  const thumb = img.clone();
  const maxSide = 900;
  const scale = Math.min(1, maxSide / Math.max(img.bitmap.width, img.bitmap.height));
  if (scale < 1) thumb.resize(Math.round(img.bitmap.width * scale), Math.round(img.bitmap.height * scale));

  // 1) Dominante Kantenwinkel schätzen (Grad in [-90, 90))
  const { peaks } = estimateEdgeAngles(thumb, { sampleStep, magThresh });
  if (peaks.length === 0) return img;

  // 2) Rotationswinkel so wählen, dass stärkste Spitze nahe 0° oder 90° liegt (kleinster Absolutwert)
  const best = bestRotationFromPeaks(peaks);
  let rotateDeg = clamp(best, -maxRotateDeg, maxRotateDeg);

  // winzige Winkel ignorieren
  if (Math.abs(rotateDeg) < 0.25) rotateDeg = 0;

  let rotated = img;
  if (rotateDeg !== 0) {
    // Jimp.rotate(rot, false) -> bilinear; keep alpha
    rotated = img.clone().rotate(rotateDeg, false);
  }

  // 3) X-Shear schätzen: nach der Rotation sollten "vertikale" Kanten nahe 0° liegen.
  //    Übrig bleibt bei leichter Perspektive oft ein kleiner Restwinkel -> als Shear kompensieren.
  const { peaks: peaks2 } = estimateEdgeAngles(rotated.clone(), { sampleStep, magThresh });
  const nearZero = pickPeakNearZero(peaks2);
  if (nearZero == null) return rotated;

  let shearDeg = clamp(nearZero, -maxShearDeg, maxShearDeg);
  if (Math.abs(shearDeg) < 0.2) return rotated;

  // Shear-Faktor k = tan(shearDeg)
  const k = Math.tan((shearDeg * Math.PI) / 180);
  const sheared = await shearX(rotated, k);
  return sheared;
}

/** Shear in X-Richtung: x' = x + k*(y - h/2). Bildgröße bleibt erhalten, Ränder werden transparent gefüllt. */
async function shearX(src: Jimp, k: number): Promise<Jimp> {
  const { width: w, height: h } = src.bitmap;
  const dst = new Jimp(w, h, 0x00000000); // transparent

  const yCenter = (h - 1) / 2;
  src.scan(0, 0, w, h, function (x, y) {
    // Quell->Ziel: einfacher Forward-Mapping
    const shift = k * (y - yCenter);
    const xd = Math.round(x + shift);
    if (xd >= 0 && xd < w) {
      const rgba = src.getPixelColor(x, y);
      dst.setPixelColor(rgba, xd, y);
    }
  });

  // Optional: kleine Lücken durch horizontale Blur/AA schlauchen (sehr mild)
  const aa = dst.clone();
  // 1px blur reicht i.d.R. um "Zahnkanten" zu glätten
  aa.blur(1);
  // Alpha-preserving blend: nur fehlende Pixel sanft ergänzen
  for (let y = 0; y < h; y++) {
    for (let x = 0; x < w; x++) {
      const c = dst.getPixelColor(x, y);
      const a = (c >>> 24) & 0xff;
      if (a < 8) {
        dst.setPixelColor(aa.getPixelColor(x, y), x, y);
      }
    }
  }

  return dst;
}

function clamp(v: number, lo: number, hi: number) {
  return Math.max(lo, Math.min(hi, v));
}

/** Gradienten-orientierte Winkel-Histogramm-Analyse (leichtgewichtige Sobel-Approx). */
function estimateEdgeAngles(img: Jimp, opts: { sampleStep: number; magThresh: number }) {
  const { width: w, height: h } = img.bitmap;
  const step = Math.max(2, opts.sampleStep | 0);
  const histBins = 180; // [-90,90)
  const hist = new Float64Array(histBins);

  // Greyscale Luma per Pixel
  const L = (x: number, y: number) => {
    const c = img.getPixelColor(x, y);
    const r = (c >> 16) & 0xff;
    const g = (c >> 8) & 0xff;
    const b = c & 0xff;
    // Rec.601 luma
    return (299 * r + 587 * g + 114 * b) / 1000;
  };

  for (let y = 1; y < h - 1; y += step) {
    for (let x = 1; x < w - 1; x += step) {
      const gx = L(x + 1, y) - L(x - 1, y); // dI/dx
      const gy = L(x, y + 1) - L(x, y - 1); // dI/dy
      const mag = Math.hypot(gx, gy);
      if (mag < opts.magThresh) continue;

      // Orientierung des Gradienten (−pi..pi), auf (−90..90] mappen
      let ang = (Math.atan2(gy, gx) * 180) / Math.PI; // Grad
      while (ang <= -90) ang += 180;
      while (ang > 90) ang -= 180;

      // Bin befüllen (Gewichtung mit Magnitude)
      const bin = Math.max(0, Math.min(histBins - 1, Math.round(((ang + 90) / 180) * (histBins - 1))));
      hist[bin] += mag;
    }
  }

  // Peaks finden (Top-2, mit Mindestabstand)
  const peaks: number[] = [];
  for (let i = 1; i < histBins - 1; i++) {
    const v = hist[i];
    if (v > hist[i - 1] && v > hist[i + 1]) peaks.push(i);
  }
  peaks.sort((a, b) => hist[b] - hist[a]); // nach Stärke

  const toAngle = (bin: number) => bin * (180 / (histBins - 1)) - 90;
  const selected: number[] = [];
  for (const p of peaks) {
    const a = toAngle(p);
    if (selected.every((s) => Math.abs(deltaAngle(a, s)) >= 25)) {
      selected.push(a);
      if (selected.length === 2) break;
    }
  }
  return { peaks: selected, hist };
}

// kleinste Differenz zweier Winkel in Grad (periodisch 180°)
function deltaAngle(a: number, b: number) {
  let d = a - b;
  while (d <= -90) d += 180;
  while (d > 90) d -= 180;
  return d;
}

/** Wähle eine kleine Rotation, die stärksten Peak auf 0° oder 90° bringt. */
function bestRotationFromPeaks(peaks: number[]): number {
  // Kandidaten: auf 0°, 90° oder -90° ausrichten -> kleinstmögliche Drehung
  let best = 0;
  let bestAbs = Infinity;
  for (const a of peaks) {
    const cand = [a, a - 90, a + 90]; // wieviel Grad bis zum Ziel 0°
    for (const c of cand) {
      const rot = -c;
      const ar = Math.abs(rot);
      if (ar < bestAbs) {
        bestAbs = ar;
        best = rot;
      }
    }
  }
  return best;
}

/** Wähle den Peak, der nach Rotation am nächsten bei 0° liegt (Restschiefe). */
function pickPeakNearZero(peaks: number[]): number | null {
  if (peaks.length === 0) return null;
  let best = peaks[0];
  for (const p of peaks) if (Math.abs(p) < Math.abs(best)) best = p;
  return best;
}

Youez - 2016 - github.com/yon3zu
LinuXploit