public function signature_scan(Request $request) { if (empty(Auth::user())) { return response()->json(['success' => false, 'message' => 'Session expired'], 401); } $request->validate([ 'document' => 'required|file|mimes:jpg,jpeg,png,pdf|max:10240', ]); try { $file = $request->file('document'); $mimeType = $file->getMimeType(); $isPdf = ($mimeType === 'application/pdf'); // ===== ขั้นที่ 1: แปลง PDF เป็น PNG (ถ้าเป็น PDF) ===== if ($isPdf) { $fileName = time() . '_' . uniqid() . '.pdf'; $pdfPath = public_path('pdf_scan/' . $fileName); $file->move(public_path('pdf_scan'), $fileName); $imagick = new \Imagick(); $imagick->setResolution(300, 300); $imagick->readImage($pdfPath . '[0]'); $imagick->setImageFormat('png'); $imagick->mergeImageLayers(\Imagick::LAYERMETHOD_FLATTEN); $imgFileName = time() . '_' . uniqid() . '.png'; $imgPath = public_path('picture_scan/' . $imgFileName); $imagick->writeImage($imgPath); $imagick->clear(); } else { $imgFileName = time() . '_' . uniqid() . '.' . $file->getClientOriginalExtension(); $imgPath = public_path('picture_scan/' . $imgFileName); $file->move(public_path('picture_scan'), $imgFileName); } // ===== ขั้นที่ 2: ใช้ GD ตรวจจับและ crop ลายเซ็นอัตโนมัติ ===== // (ไม่ใช้ Gemini เลย — stable 100% ทุกครั้งสำหรับไฟล์เดิม) $sourceImage = imagecreatefrompng($imgPath); $srcW = imagesx($sourceImage); $srcH = imagesy($sourceImage); // หาขอบของลายเซ็นโดยหาพิกเซลที่ไม่ใช่สีขาว/พื้นหลัง // Threshold: pixel ที่มีค่า RGB ต่ำกว่านี้ถือว่าเป็นลายเซ็น (0=ดำ, 255=ขาว) $threshold = 200; $minX = $srcW; $minY = $srcH; $maxX = 0; $maxY = 0; $found = false; for ($y = 0; $y < $srcH; $y++) { for ($x = 0; $x < $srcW; $x++) { $rgb = imagecolorat($sourceImage, $x, $y); $r = ($rgb >> 16) & 0xFF; $g = ($rgb >> 8) & 0xFF; $b = $rgb & 0xFF; // ถ้าพิกเซลนี้เข้มพอ (ไม่ใช่พื้นขาว) = เป็นส่วนหนึ่งของลายเซ็น if ($r < $threshold || $g < $threshold || $b < $threshold) { if ($x < $minX) $minX = $x; if ($y < $minY) $minY = $y; if ($x > $maxX) $maxX = $x; if ($y > $maxY) $maxY = $y; $found = true; } } } if (!$found) { imagedestroy($sourceImage); return response()->json(['success' => false, 'message' => 'ไม่พบลายเซ็นในเอกสาร กรุณาตรวจสอบว่าไฟล์มีลายเซ็นจริงๆ']); } // เพิ่ม padding รอบลายเซ็น (หน่วย: pixel) $padding = 20; $cropX = max(0, $minX - $padding); $cropY = max(0, $minY - $padding); $cropW = min($srcW - $cropX, ($maxX - $minX) + ($padding * 2)); $cropH = min($srcH - $cropY, ($maxY - $minY) + ($padding * 2)); // Crop รูปพร้อมพื้นหลังโปร่งใส $croppedImage = imagecreatetruecolor($cropW, $cropH); imagealphablending($croppedImage, false); imagesavealpha($croppedImage, true); $transparent = imagecolorallocatealpha($croppedImage, 255, 255, 255, 127); imagefill($croppedImage, 0, 0, $transparent); imagecopy($croppedImage, $sourceImage, 0, 0, $cropX, $cropY, $cropW, $cropH); // บันทึกรูปที่ crop แล้ว $cropFileName = 'sig_' . time() . '_' . uniqid() . '.png'; $cropPath = storage_path('app/signatures/' . $cropFileName); if (!file_exists(storage_path('app/signatures'))) { mkdir(storage_path('app/signatures'), 0777, true); } imagepng($croppedImage, $cropPath); imagedestroy($sourceImage); imagedestroy($croppedImage); // ===== ขั้นที่ 3: ส่งผลลัพธ์กลับ ===== // ชื่อและตำแหน่งเป็นค่าว่าง ให้ผู้ใช้กรอกเองในฟอร์ม return response()->json([ 'success' => true, 'name' => '', // ผู้ใช้กรอกเองใน form 'position' => '', // ผู้ใช้กรอกเองใน form 'preview_url' => route('signature.view', ['filename' => $cropFileName]), 'save_path' => 'signatures/' . $cropFileName, ]); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => $e->getMessage()], 500); } }