TKK_E32230273/picture.php

326 lines
12 KiB
PHP

<?php
// ══════════════════════════════════════════════════════════════
// picture.php — Fingerprint Image Receiver & Gallery
// ══════════════════════════════════════════════════════════════
// Menerima raw image data dari ESP32, konversi ke PNG,
// dan menampilkan galeri untuk download.
// ══════════════════════════════════════════════════════════════
$uploadDir = __DIR__ . '/fingerprints/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0777, true);
}
// ════════════════════════════════════════════════════════════
// HANDLE POST — Terima gambar dari ESP32
// ════════════════════════════════════════════════════════════
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
header('Content-Type: text/plain');
$type = $_GET['type'] ?? 'unknown';
$width = intval($_GET['w'] ?? 256);
$height = intval($_GET['h'] ?? 288);
$rawData = file_get_contents('php://input');
$rawLen = strlen($rawData);
if ($rawLen < 100) {
http_response_code(400);
echo "ERROR: Data terlalu kecil ($rawLen bytes)";
exit;
}
// Buat gambar grayscale dari raw 4-bit packed data
// Setiap byte = 2 pixel (high nibble = pixel 1, low nibble = pixel 2)
$img = imagecreatetruecolor($width, $height);
// Buat warna grayscale
$colors = [];
for ($i = 0; $i < 256; $i++) {
$colors[$i] = imagecolorallocate($img, $i, $i, $i);
}
$offset = 0;
for ($y = 0; $y < $height; $y++) {
for ($x = 0; $x < $width; $x += 2) {
if ($offset < $rawLen) {
$byte = ord($rawData[$offset++]);
$pixel1 = (($byte >> 4) & 0x0F) * 17; // 0-15 → 0-255
$pixel2 = ($byte & 0x0F) * 17;
imagesetpixel($img, $x, $y, $colors[$pixel1]);
if ($x + 1 < $width) {
imagesetpixel($img, $x + 1, $y, $colors[$pixel2]);
}
}
}
}
// Simpan sebagai PNG
$timestamp = date('Y-m-d_H-i-s');
$filename = "finger_{$type}_{$timestamp}.png";
$filepath = $uploadDir . $filename;
imagepng($img, $filepath);
imagedestroy($img);
// Juga simpan raw data untuk analisis
$rawFilename = "finger_{$type}_{$timestamp}.raw";
file_put_contents($uploadDir . $rawFilename, $rawData);
echo "OK: $filename ($rawLen bytes)";
error_log("[FINGERPRINT] Saved: $filename | Type: $type | Raw: $rawLen bytes | Size: {$width}x{$height}");
exit;
}
// ════════════════════════════════════════════════════════════
// HANDLE GET — Tampilkan galeri gambar
// ════════════════════════════════════════════════════════════
// Handle download
if (isset($_GET['download'])) {
$file = basename($_GET['download']); // Prevent path traversal
$path = $uploadDir . $file;
if (file_exists($path) && pathinfo($path, PATHINFO_EXTENSION) === 'png') {
header('Content-Type: image/png');
header('Content-Disposition: attachment; filename="' . $file . '"');
header('Content-Length: ' . filesize($path));
readfile($path);
exit;
}
}
// Handle delete
if (isset($_GET['delete'])) {
$file = basename($_GET['delete']);
$path = $uploadDir . $file;
$rawPath = str_replace('.png', '.raw', $path);
if (file_exists($path)) unlink($path);
if (file_exists($rawPath)) unlink($rawPath);
header('Location: picture.php');
exit;
}
// Handle delete all
if (isset($_GET['delete_all'])) {
$files = glob($uploadDir . 'finger_*');
foreach ($files as $f) {
unlink($f);
}
header('Location: picture.php');
exit;
}
// Scan directory for images
$images = glob($uploadDir . 'finger_*.png');
rsort($images); // Terbaru di atas
$totalImages = count($images);
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fingerprint Image Gallery</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Inter', sans-serif;
background: #0f172a;
color: #e2e8f0;
min-height: 100vh;
}
.header {
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
border-bottom: 1px solid #1e293b;
padding: 20px 30px;
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
font-size: 22px;
font-weight: 700;
color: #f8fafc;
}
.header h1 span { color: #38bdf8; }
.header .stats {
font-size: 13px;
color: #94a3b8;
}
.actions {
padding: 15px 30px;
display: flex;
gap: 10px;
flex-wrap: wrap;
align-items: center;
border-bottom: 1px solid #1e293b;
}
.btn {
padding: 8px 16px;
border-radius: 6px;
border: 1px solid #334155;
background: #1e293b;
color: #e2e8f0;
font-size: 13px;
cursor: pointer;
text-decoration: none;
transition: all 0.2s;
}
.btn:hover { background: #334155; }
.btn-danger { border-color: #7f1d1d; color: #fca5a5; }
.btn-danger:hover { background: #7f1d1d; }
.filter-tag {
padding: 5px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
cursor: pointer;
border: 1px solid #334155;
background: transparent;
color: #94a3b8;
transition: all 0.2s;
}
.filter-tag.active, .filter-tag:hover { background: #334155; color: #f8fafc; }
.filter-tag.bersih.active { background: #064e3b; color: #6ee7b7; border-color: #065f46; }
.filter-tag.basah.active { background: #0c4a6e; color: #7dd3fc; border-color: #075985; }
.filter-tag.kotor.active { background: #7c2d12; color: #fdba74; border-color: #9a3412; }
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
padding: 25px 30px;
}
.card {
background: #1e293b;
border-radius: 12px;
overflow: hidden;
border: 1px solid #334155;
transition: transform 0.2s, box-shadow 0.2s;
}
.card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(0,0,0,0.3);
}
.card img {
width: 100%;
height: auto;
display: block;
background: #000;
image-rendering: pixelated;
}
.card-info {
padding: 12px 15px;
}
.card-type {
display: inline-block;
padding: 3px 10px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.type-bersih { background: #064e3b; color: #6ee7b7; }
.type-basah { background: #0c4a6e; color: #7dd3fc; }
.type-kotor { background: #7c2d12; color: #fdba74; }
.type-unknown { background: #334155; color: #94a3b8; }
.card-date {
font-size: 12px;
color: #64748b;
margin-top: 6px;
}
.card-actions {
display: flex;
gap: 8px;
margin-top: 10px;
}
.card-actions a {
font-size: 12px;
padding: 4px 10px;
border-radius: 4px;
text-decoration: none;
border: 1px solid #334155;
color: #94a3b8;
transition: all 0.2s;
}
.card-actions a:hover { background: #334155; color: #f8fafc; }
.card-actions a.del:hover { background: #7f1d1d; color: #fca5a5; }
.empty {
text-align: center;
padding: 80px 30px;
color: #475569;
}
.empty h2 { font-size: 20px; margin-bottom: 8px; color: #64748b; }
.empty p { font-size: 14px; }
</style>
</head>
<body>
<div class="header">
<h1><span>&#128270;</span> Fingerprint <span>Gallery</span></h1>
<div class="stats"><?= $totalImages ?> gambar</div>
</div>
<div class="actions">
<span style="font-size:13px; color:#64748b; margin-right:5px;">Filter:</span>
<button class="filter-tag active" onclick="filterImages('all')">Semua</button>
<button class="filter-tag bersih" onclick="filterImages('BERSIH')">Bersih</button>
<button class="filter-tag basah" onclick="filterImages('BASAH')">Basah</button>
<button class="filter-tag kotor" onclick="filterImages('KOTOR')">Kotor</button>
<div style="flex:1"></div>
<?php if ($totalImages > 0): ?>
<a href="?delete_all=1" class="btn btn-danger" onclick="return confirm('Hapus SEMUA gambar?')">Hapus Semua</a>
<?php endif; ?>
</div>
<?php if ($totalImages === 0): ?>
<div class="empty">
<h2>Belum ada gambar</h2>
<p>Upload gambar sidik jari dari ESP32 untuk melihatnya di sini.</p>
</div>
<?php else: ?>
<div class="grid">
<?php foreach ($images as $imgPath):
$fname = basename($imgPath);
// Parse filename: finger_TYPE_DATE.png
preg_match('/finger_(\w+)_(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})\.png/', $fname, $m);
$type = $m[1] ?? 'unknown';
$dateStr = isset($m[2]) ? str_replace('_', ' ', str_replace('-', ':', substr($m[2], 11))) : '';
$dateStr = isset($m[2]) ? substr($m[2], 0, 10) . ' ' . str_replace('-', ':', substr($m[2], 11)) : $fname;
$typeClass = 'type-' . strtolower($type);
$filesize = filesize($imgPath);
?>
<div class="card" data-type="<?= strtoupper($type) ?>">
<img src="fingerprints/<?= $fname ?>" alt="<?= $fname ?>" loading="lazy">
<div class="card-info">
<span class="card-type <?= $typeClass ?>"><?= strtoupper($type) ?></span>
<div class="card-date"><?= $dateStr ?> &middot; <?= number_format($filesize / 1024, 1) ?> KB</div>
<div class="card-actions">
<a href="?download=<?= urlencode($fname) ?>">&#11015; Download</a>
<a href="fingerprints/<?= $fname ?>" target="_blank">&#128269; Lihat</a>
<a href="?delete=<?= urlencode($fname) ?>" class="del" onclick="return confirm('Hapus?')">&#128465; Hapus</a>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<script>
function filterImages(type) {
document.querySelectorAll('.filter-tag').forEach(t => t.classList.remove('active'));
event.target.classList.add('active');
document.querySelectorAll('.card').forEach(card => {
if (type === 'all' || card.dataset.type === type) {
card.style.display = '';
} else {
card.style.display = 'none';
}
});
}
</script>
</body>
</html>