
7
●472

<?php
/**
* ARCADE GERI SAYIM SAYACI — animasyonlu GIF
* ---------------------------------------------------------------
*
* NEDEN GIF?
* PNG durgun bir resimdir; sayfa yenilenmeden degismez. GIF ise
* animasyon tasiyabildigi icin, sunucu istek geldigi anda 60
* saniyelik bir animasyon uretir ve tarayici bunu kendi oynatir.
* Sonuc: sayac sayfa yenilenmeden tiklar. Forumlar <img> etiketine
* izin verdigi icin de konu icinde, imzada, her yerde calisir.
*
* KULLANIM
* HTML : <img src="https://siteniz.com/index.php">
* BBCode : [img]https://siteniz.com/index.php[/img]
*
* TEMALAR (adresin sonuna eklenir)
* ?tema=koyu koyu zemin (varsayilan)
* ?tema=acik acik zemin
* ?tema=seffaf zemin saydam; forum kendi arka planini gosterir,
* yani hem koyu hem acik temali forumda uyar
*
* KURULUM
* 1) Bu dosyayi sunucuya atin.
* 2) Yanina "onbellek" adinda bos bir klasor acip yazma izni
* verin (755). Acmazsaniz yine calisir, sadece her istekte
* bastan uretir - yani daha yavas olur.
* 3) Turkce karakter (GUN yerine GÜN) isterseniz ayni klasore
* "font.ttf" atin. Rakamlar fonta ihtiyac duymaz, onlari kod
* kendi cizer; font yalnizca alt etiketler icindir.
*
* GEREKSINIM
* PHP + GD eklentisi (imagegif). Baska kutuphane yok.
*
* NOT
* Animasyon 60 saniyeliktir; sonunda son karede durur. Sayfa
* yenilendiginde bastan uretilir. Daha uzun sursun isterseniz
* asagidaki $KARE_SAYISI degerini buyutun (dosya boyutu artar).
*/
// ==================================================================
// AYARLAR — duzenlemeniz gereken tek bolum burasi
// ==================================================================
date_default_timezone_set('Europe/Istanbul');
// Geri sayimin bitecegi an. Bicim: 'YIL-AY-GUN SAAT:DAKIKA:SANIYE'
$HEDEF = '2026-09-15 14:13:00';
// Sayacin ustundeki tabelada yazan metin
$BASLIK = 'UGILABS - GIF Versiyon';
// Sure doldugunda ekranda yazacak metin
$BITTI_METNI = 'OYUN BITTI';
// ------------------------------------------------------------------
// ANIMASYON
// ------------------------------------------------------------------
// Kac saniyelik animasyon uretilsin. 60 = 1 dakika.
// Buyutursen daha uzun sure tiklar ama dosya buyur:
// 60 kare ~75 KB (1 dakika)
// 300 kare ~250 KB (5 dakika)
$KARE_SAYISI = 60;
// Her karenin ekranda kalma suresi (1/100 saniye cinsinden).
// 100 = 1 saniye. Sayac saniye saydigi icin bunu degistirme.
$KARE_SURESI = 100;
// 1 = bir kez oynar, sonra son karede durur.
// 0 = sonsuz doner. Surekli hareket eder ama her turda basa
// saracagi icin yanlis zaman gostermeye baslar. Sayac icin
// 1 dogrusu; sonsuz dongu ancak susleme amacli isine yarar.
$DONGU = 1;
// false yaparsan saniye hanesi kaybolur, sadece gun/saat/dakika
// kalir. O durumda animasyona gerek kalmaz, tek kare uretilir.
$SANIYE_GOSTER = true;
// ------------------------------------------------------------------
// ONBELLEK — paylasimli hostingde sunucuyu ayakta tutar
//
// Her istekte animasyonu bastan cizmek sunucuyu yorar (~0.8 sn).
// Populer bir konuda 50 kisi ayni anda sayfayi acarsa bu 40 saniye
// islemci demektir ve paylasimli hosting bunu kaldirmaz.
//
// Bu yuzden uretilen GIF diske yazilir; asagidaki sure boyunca
// gelen istekler yeniden cizmeden hazir dosyayi alir. 50 kisi ayni
// anda girse bile tek uretim yapilir.
//
// Sayacin dogrulugunu bozmaz: en fazla bu sure kadar geriden gelir,
// yani 3 saniyede kimsenin fark edecegi bir sapma olusmaz.
// ------------------------------------------------------------------
// Uretilen dosya kac saniye saklansin. 0 yazarsan onbellek kapanir.
$ONBELLEK_SN = 3;
// Dosyalarin yazilacagi klasor. Yazma izni yoksa sistem yine
// calisir, sadece her istekte bastan uretir.
$ONBELLEK_DIZ = __DIR__ . '/onbellek';
// ------------------------------------------------------------------
// FONT
// Rakamlar fonta ihtiyac duymaz - onlari kod nokta nokta cizer.
// Buradaki font yalnizca alt etiketler (GUN, SAAT...) ve baslik
// icindir. Turkce karakter isterseniz klasore font.ttf atin;
// bulunamazsa asagidaki sistem fontlari sirayla denenir.
// ------------------------------------------------------------------
$FONT_ADAYLARI = [
__DIR__ . '/font.ttf',
'C:/Windows/Fonts/consolab.ttf',
'/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',
'/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',
];
// ------------------------------------------------------------------
// TEMA — arcade dolabinin ekrani
// ------------------------------------------------------------------
$tema = isset($_GET['tema']) ? strtolower($_GET['tema']) : 'koyu';
if (!in_array($tema, ['koyu', 'acik', 'seffaf'], true)) { $tema = 'koyu'; }
$T = [
// Ortak arcade paleti - zeminden bagimsiz, her iki temada da okunur.
'siyan' => [ 34, 224, 232],
'magenta' => [255, 61, 139],
'amber' => [255, 197, 61],
'mor' => [ 27, 16, 53],
'ten' => [217, 142, 95],
'tulum' => [ 46, 92, 138],
'tugla' => [166, 74, 58],
'tuglaKen' => [ 92, 38, 30],
];
// 'ekran' her temada koyu kalir - CRT camı zeminden bagimsizdir.
// 'baslik' : ekranin ICINDEKI etiketler (hep koyu zemin uzerinde)
// 'marquee' : ekranin USTUNDEKI tabela yazisi (zemine gore degisir)
if ($tema === 'acik') {
$T['zemin'] = [232, 230, 240];
$T['ekran'] = [ 38, 24, 70];
$T['cerceve'] = [ 62, 44, 104];
$T['baslik'] = [166, 152, 208];
$T['marquee'] = [ 44, 30, 84]; // acik zeminde koyu yazi
$T['golge'] = [206, 202, 220];
} else {
$T['zemin'] = [ 16, 10, 30];
$T['ekran'] = [ 30, 18, 58];
$T['cerceve'] = [ 62, 44, 104];
$T['baslik'] = [148, 132, 196];
$T['marquee'] = [172, 156, 216];
$T['golge'] = [ 10, 6, 20];
}
// ==================================================================
// ONBELLEKTEN SUNMA
//
// Bu bolum dosyanin en basinda duruyor cunku amaci cizim islerine
// hic girmeden isi bitirmek. Yeterince taze bir dosya varsa onu
// gonderip cikiyoruz; asagidaki kodun tamami hic calismiyor.
// ==================================================================
/** Hazir GIF verisini tarayiciya gonderir ve betigi sonlandirir. */
function gifGonder($veri)
{
header('Content-Type: image/gif');
header('Content-Length: ' . strlen($veri));
// Tarayiciya "bu resmi saklama" diyoruz. Saklarsa sayfa
// yenilendiginde eski animasyonu gosterir ve sayac yanlis
// zamanda kalir.
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Expires: Sat, 01 Jan 2000 00:00:00 GMT');
echo $veri;
exit;
}
$onbellekDosya = null;
if ($ONBELLEK_SN > 0) {
// Dosya adini ayarlardan uretiyoruz: ayni ayarlar ayni dosyayi
// kullanir, farkli tema kendi dosyasini alir. Boylece temalar
// birbirine karismaz.
$anahtar = md5(implode('|', [
$HEDEF, $BASLIK, $BITTI_METNI, $tema,
$KARE_SAYISI, $KARE_SURESI, $DONGU, $SANIYE_GOSTER,
]));
if (!is_dir($ONBELLEK_DIZ)) { @mkdir($ONBELLEK_DIZ, 0755, true); }
$onbellekDosya = $ONBELLEK_DIZ . '/' . $anahtar . '.gif';
// Dosya var ve yeterince taze mi? Oyleyse hic cizmeden gonder.
if (is_readable($onbellekDosya)) {
$yas = time() - (int) @filemtime($onbellekDosya);
if ($yas >= 0 && $yas < $ONBELLEK_SN) {
$hazir = @file_get_contents($onbellekDosya);
if ($hazir !== false && strlen($hazir) > 0) {
gifGonder($hazir);
}
}
}
// Buraya dustuyse dosya yok ya da eskimis; asagida yeniden
// uretilip en sonda diske yazilacak.
}
// ==================================================================
// HAZIRLIK
// ==================================================================
$FONT = null;
foreach ($FONT_ADAYLARI as $f) {
if (is_readable($f)) { $FONT = $f; break; }
}
$ttf = ($FONT !== null);
$hedefTs = strtotime($HEDEF);
$simdi = time();
if (!$SANIYE_GOSTER) { $KARE_SAYISI = 1; }
$ETIKETLER = ['GUN', 'SAAT', 'DAKIKA', 'SANIYE'];
$ADET = $SANIYE_GOSTER ? 4 : 3;
// ------------------------------------------------------------------
// OLCULER
// Ekran bir arcade dolabinin CRT'si: disinda cerceve, icinde rakamlar,
// en altta Ralph'in yumruk attigi tugla siras var.
// ------------------------------------------------------------------
$PADDING = 14; // dis cerceve kalinligi
// Rakamlar 5x7 glif izgarasi. $PIX bir glif pikselinin blok boyutu.
$PIX = 6;
$HANE_G = 5 * $PIX; // bir rakam hanesinin genisligi
$RAKAM_YUK = 7 * $PIX; // glif yuksekligi
$HANE_BOS = $PIX; // ayni grup icindeki haneler arasi
$GRUP_BOS = $PIX * 3; // gruplar arasi (iki nokta buraya gelir)
// Gun hanesi 3 basamak olabilir (999 gun), digerleri hep 2 basamak.
$gunBasamak = max(2, strlen((string) (int) floor(max(0, $hedefTs - $simdi) / 86400)));
$BASAMAKLAR = [$gunBasamak, 2, 2, 2];
$BASAMAKLAR = array_slice($BASAMAKLAR, 0, $ADET);
$grupGen = [];
foreach ($BASAMAKLAR as $n) {
$grupGen[] = $n * $HANE_G + ($n - 1) * $HANE_BOS;
}
// Rakam bloklarinin toplam genisligi
$RAKAM_G = array_sum($grupGen) + ($ADET - 1) * $GRUP_BOS;
// Ralph rakamlarin SAGINDA kendi seridinde durur - etiketleri kapatmaz.
$RALPH_P = 3; // Ralph'in piksel blok boyutu
$RALPH_G = 13 * $RALPH_P; // Ralph + yumruk icin ayrilan genislik
$RALPH_BOS = 14; // rakamlarla Ralph arasi bosluk
$EKRAN_G = $RAKAM_G + $RALPH_BOS + $RALPH_G + 16;
$EKRAN_SOL = $PADDING;
$EKRAN_UST = 42; // ustte baslik seridi
$EKRAN_YUK = 92; // rakamlar + etiketler
$TUGLA_YUK = 26; // Ralph'in kirdigi tugla sirasi
$W = $EKRAN_G + $PADDING * 2;
$H = $EKRAN_UST + $EKRAN_YUK + $TUGLA_YUK + $PADDING;
// Rakamlarin dikey konumu ($RAKAM_YUK yukarida glif izgarasindan geldi)
$RAKAM_UST = $EKRAN_UST + 16;
$ETIKET_Y = $RAKAM_UST + $RAKAM_YUK + 16;
// ------------------------------------------------------------------
// SEFFAF TEMA ICIN ISARET RENGI
//
// GIF'te saydamlik, "su renk saydam sayilsin" diye tek bir rengi
// isaretleyerek olur. Dogrudan saydam cizemeyiz, cunku GIF'e
// cevirirken kullanilan imagetruecolortopalette() saydamlik
// bilgisini korumaz.
//
// Bu yuzden zemini once bu renkle boyuyoruz - tasarimda hic
// gecmeyen, kimseye benzemeyen parlak bir ton. GIF'e yazarken de
// "bu renk saydamdir" diye isaretliyoruz. Sonuc: forumun kendi
// arka plani resmin altindan gorunuyor.
// ------------------------------------------------------------------
define('SEFFAF_R', 255);
define('SEFFAF_G', 0);
define('SEFFAF_B', 254);
// ==================================================================
// ARCADE PIKSEL FONT
// Rakamlar 5x7 izgaraya cizilmis gercek bitmap glifler. Her "piksel"
// ekranda $p x $p'lik bir blok olur - klasik arcade skor tabelasi gibi.
// TTF'e bagli degil, her sunucuda birebir ayni gorunur.
// ==================================================================
/** 5x7 rakam glifleri. Her satir 5 karakter: '#' dolu, '.' bos. */
function glif($ch)
{
static $font = [
'0' => ['.###.',
'#...#',
'#..##',
'#.#.#',
'##..#',
'#...#',
'.###.'],
'1' => ['..#..',
'.##..',
'..#..',
'..#..',
'..#..',
'..#..',
'.###.'],
'2' => ['.###.',
'#...#',
'....#',
'...#.',
'..#..',
'.#...',
'#####'],
'3' => ['#####',
'...#.',
'..#..',
'...#.',
'....#',
'#...#',
'.###.'],
'4' => ['...#.',
'..##.',
'.#.#.',
'#..#.',
'#####',
'...#.',
'...#.'],
'5' => ['#####',
'#....',
'####.',
'....#',
'....#',
'#...#',
'.###.'],
'6' => ['..##.',
'.#...',
'#....',
'####.',
'#...#',
'#...#',
'.###.'],
'7' => ['#####',
'....#',
'...#.',
'..#..',
'.#...',
'.#...',
'.#...'],
'8' => ['.###.',
'#...#',
'#...#',
'.###.',
'#...#',
'#...#',
'.###.'],
'9' => ['.###.',
'#...#',
'#...#',
'.####',
'....#',
'...#.',
'.##..'],
];
return isset($font[$ch]) ? $font[$ch] : null;
}
/**
* Tek bir rakami piksel bloklariyla cizer.
*
* $p : bir glif pikselinin ekrandaki blok boyutu
* $acik : dolu piksel rengi
* $govde : blogun ust kenarinda kullanilan koyu ton (hacim hissi)
*/
function rakamCiz($im, $rakam, $x, $y, $p, $acik, $govde)
{
$g = glif($rakam);
if ($g === null) { return; }
foreach ($g as $sy => $satir) {
for ($sx = 0; $sx < 5; $sx++) {
if ($satir[$sx] !== '#') { continue; }
$x1 = $x + $sx * $p;
$y1 = $y + $sy * $p;
$x2 = $x1 + $p - 1;
$y2 = $y1 + $p - 1;
imagefilledrectangle($im, $x1, $y1, $x2, $y2, $acik);
// Her blogun alt-sag kenarinda bir tik koyu ton: pikseller
// birbirine yapismaz, izgara okunur kalir.
imageline($im, $x1, $y2, $x2, $y2, $govde);
imageline($im, $x2, $y1, $x2, $y2, $govde);
}
}
}
/** Iki nokta ayirac - glif izgarasiyla ayni blok boyutunda. */
function ayiracCiz($im, $x, $y, $p, $renk, $govde)
{
foreach ([1, 4] as $sy) {
$y1 = $y + $sy * $p + (int) round($p / 2);
imagefilledrectangle($im, $x, $y1, $x + $p - 1, $y1 + $p - 1, $renk);
imageline($im, $x, $y1 + $p - 1, $x + $p - 1, $y1 + $p - 1, $govde);
}
}
// ==================================================================
// CIZIM YARDIMCILARI
// ==================================================================
/** Yatayda ortalanmis metin. Harf araligi (tracking) destekli. */
function yaz($im, $metin, $boyut, $merkezX, $baselineY, $renk, $font, $ttf, $aralik = 0)
{
if ($ttf && $aralik > 0) {
// Harf harf cizip aralik veriyoruz - arcade etiketleri genis olur.
$harfler = preg_split('//u', $metin, -1, PREG_SPLIT_NO_EMPTY);
$toplam = 0;
$genler = [];
foreach ($harfler as $h) {
$bb = imagettfbbox($boyut, 0, $font, $h);
$g = $bb[2] - $bb[0];
$genler[] = $g;
$toplam += $g + $aralik;
}
$toplam -= $aralik;
$x = (int) ($merkezX - $toplam / 2);
foreach ($harfler as $i => $h) {
imagettftext($im, $boyut, 0, $x, $baselineY, $renk, $font, $h);
$x += $genler[$i] + $aralik;
}
return;
}
if ($ttf) {
$bbox = imagettfbbox($boyut, 0, $font, $metin);
$x = (int) ($merkezX - ($bbox[2] - $bbox[0]) / 2);
imagettftext($im, $boyut, 0, $x, $baselineY, $renk, $font, $metin);
} else {
$f = $boyut >= 18 ? 5 : ($boyut >= 11 ? 3 : 2);
// Dahili fontta aralik icin harf harf cizim
if ($aralik > 0) {
$bg = imagefontwidth($f);
$toplam = strlen($metin) * ($bg + $aralik) - $aralik;
$x = (int) ($merkezX - $toplam / 2);
$y = (int) ($baselineY - imagefontheight($f));
for ($i = 0; $i < strlen($metin); $i++) {
imagestring($im, $f, $x, $y, $metin[$i], $renk);
$x += $bg + $aralik;
}
return;
}
$x = (int) ($merkezX - imagefontwidth($f) * strlen($metin) / 2);
$y = (int) ($baselineY - imagefontheight($f));
imagestring($im, $f, $x, $y, $metin, $renk);
}
}
/** Belirli bir zaman icin hane metinlerini dondurur. */
function degerler($kalan, $adet, $gunBasamak)
{
if ($kalan < 0) { $kalan = 0; }
$v = [
str_pad((string) (int) floor($kalan / 86400), $gunBasamak, '0', STR_PAD_LEFT),
str_pad((string) (int) floor(($kalan % 86400) / 3600), 2, '0', STR_PAD_LEFT),
str_pad((string) (int) floor(($kalan % 3600) / 60), 2, '0', STR_PAD_LEFT),
str_pad((string) (int) ($kalan % 60), 2, '0', STR_PAD_LEFT),
];
return array_slice($v, 0, $adet);
}
// ------------------------------------------------------------------
// RALPH
//
// Karakteri hazir bir resimden almiyoruz, blok blok ciziyoruz.
// Kocaman yumruklar, mavi tulum, dagink portakal sac.
//
// Asagidaki $b() yardimcisi isi kolaylastiriyor: koordinatlari
// piksel yerine kucuk kareler halinde veriyoruz, o da $p ile
// carpip ekrana ciziyor. Yani $b(3, 2, 5, 3) demek "soldan 3.,
// yukaridan 2. kareden basla, 5 kare genis 3 kare yuksek bir
// dikdortgen doldur" demek.
// ------------------------------------------------------------------
/**
* Ralph'i cizer.
*
* $vurus yumrugun nerede oldugunu belirler:
* 0 = kol yukarida (hazirlaniyor)
* 1 = yumruk asagida (tuglaya indi)
* Arasindaki degerler ara konumlari verir.
*/
function ralphCiz($im, $x, $y, $p, $vurus, $renkler)
{
list($ten, $tulum, $sac, $koyu, $ayakkabi) = $renkler;
$b = function ($gx, $gy, $gw, $gh, $renk) use ($im, $x, $y, $p) {
imagefilledrectangle(
$im,
$x + $gx * $p,
$y + $gy * $p,
$x + ($gx + $gw) * $p - 1,
$y + ($gy + $gh) * $p - 1,
$renk
);
};
// Vurusta govde one dogru cokuyor
$eg = (int) round($vurus);
// --- Sac (portakal, dagink) ---
$b(3, 0 + $eg, 5, 1, $sac);
$b(2, 1 + $eg, 7, 1, $sac);
$b(2, 0 + $eg, 1, 1, $sac); // dagink tutamlar
$b(8, 0 + $eg, 1, 1, $sac);
// --- Yuz ---
$b(3, 2 + $eg, 5, 3, $ten);
$b(3, 3 + $eg, 1, 1, $koyu); // sol goz
$b(6, 3 + $eg, 1, 1, $koyu); // sag goz
$b(4, 4 + $eg, 3, 1, $koyu); // agiz - kararli ifade
// --- Govde (tulum) ---
$b(3, 5 + $eg, 5, 4, $tulum);
$b(4, 5 + $eg, 1, 1, $koyu); // tulum askilari
$b(6, 5 + $eg, 1, 1, $koyu);
// --- Sol kol + dev yumruk (sabit, yanda asili) ---
$b(2, 6 + $eg, 1, 2, $ten);
$b(0, 8 + $eg, 3, 3, $ten); // Ralph'in meshur kocaman eli
$b(0, 8 + $eg, 3, 1, $koyu);
// --- Sag kol + dev yumruk: vurusa gore yukaridan tuglalara iner ---
// $vurus 0 -> yumruk basin uzerinde, 1 -> yumruk zeminde
$yumrukY = (int) round(-2 + $vurus * 11);
// Omuzdan yumruga uzanan kol
$omuzY = 6 + $eg;
$ust = min($omuzY, $yumrukY + $eg);
$alt = max($omuzY, $yumrukY + $eg + 2);
$b(8, $ust, 1, max(1, $alt - $ust), $ten);
// Yumruk (3x3 blok - govdeden buyuk, karakterin imzasi)
$b(8, $yumrukY + $eg, 3, 3, $ten);
$b(8, $yumrukY + $eg, 3, 1, $koyu); // parmak eklemleri golgesi
// --- Bacaklar ---
$b(3, 9 + $eg, 2, 2, $tulum);
$b(6, 9 + $eg, 2, 2, $tulum);
$b(2, 11 + $eg, 3, 1, $ayakkabi);
$b(6, 11 + $eg, 3, 1, $ayakkabi);
}
/**
* Tugla sirasi. $kirikIndex >= 0 ise o tugla parcalanmis cizilir.
*/
function tuglaCiz($im, $sol, $sag, $ust, $yuk, $kirikIndex, $renkler)
{
list($tugla, $ken, $toz) = $renkler;
$tg = 26; // tugla genisligi
$adet = (int) ceil(($sag - $sol) / $tg);
for ($i = 0; $i < $adet; $i++) {
$x1 = $sol + $i * $tg;
$x2 = min($x1 + $tg - 2, $sag);
if ($x1 >= $sag) { break; }
if ($i === $kirikIndex) {
// Parcalanmis tugla: ust yarisi ucmus, altta molozlar
imagefilledrectangle($im, $x1, $ust + (int) ($yuk * 0.55), $x2, $ust + $yuk, $ken);
// Ucusan parcalar
imagefilledrectangle($im, $x1 + 3, $ust - 5, $x1 + 7, $ust - 1, $toz);
imagefilledrectangle($im, $x1 + 13, $ust - 8, $x1 + 16, $ust - 5, $toz);
imagefilledrectangle($im, $x2 - 5, $ust - 3, $x2 - 2, $ust, $toz);
continue;
}
imagefilledrectangle($im, $x1, $ust, $x2, $ust + $yuk, $tugla);
imagerectangle($im, $x1, $ust, $x2, $ust + $yuk, $ken);
// Ust kenar isigi
imageline($im, $x1 + 1, $ust + 1, $x2 - 1, $ust + 1, $ken);
}
}
// ==================================================================
// KARE CIZIMI
// ==================================================================
/**
* Tek bir kareyi cizer.
*
* @param int $kalan Kalan saniye
* @param int $faz Animasyon fazi (0..N) - yumruk ve titresim icin
*/
function kareCiz($kalan, $faz)
{
global $W, $H, $ADET, $PADDING, $PIX, $HANE_G, $HANE_BOS, $GRUP_BOS, $BASAMAKLAR,
$EKRAN_SOL, $EKRAN_UST, $EKRAN_G, $EKRAN_YUK, $TUGLA_YUK,
$RAKAM_UST, $RAKAM_YUK, $ETIKET_Y, $grupGen,
$RALPH_P, $RALPH_G, $RALPH_BOS,
$BASLIK, $BITTI_METNI, $ETIKETLER, $FONT, $ttf, $T, $tema, $gunBasamak;
$im = imagecreatetruecolor($W, $H);
imagealphablending($im, false);
imagesavealpha($im, true);
$r = function ($c, $a = 0) use ($im) {
return imagecolorallocatealpha($im, $c[0], $c[1], $c[2], $a);
};
// ---------- ZEMIN ----------
// Seffaf temada zemini isaret rengiyle boyuyoruz; o renk daha
// sonra GIF'e yazilirken saydam olarak isaretleniyor.
// (Ayrintili aciklama icin yukaridaki SEFFAF_R tanimina bakin.)
if ($tema === 'seffaf') {
imagefilledrectangle(
$im, 0, 0, $W - 1, $H - 1,
imagecolorallocate($im, SEFFAF_R, SEFFAF_G, SEFFAF_B)
);
} else {
imagefilledrectangle($im, 0, 0, $W - 1, $H - 1, $r($T['zemin']));
}
imagealphablending($im, true);
// ---------- DOLAP CERCEVESI ----------
// Ekranin cevresinde ince cift cizgi - arcade dolabinin bezel'i.
// Seffaf temada yari saydam tonlar nobetci rengi karistiracagi icin
// tum cerceve ogeleri opak cizilir.
$sf = ($tema === 'seffaf');
$cerceve = $r($T['cerceve']);
imagerectangle($im, 1, 1, $W - 2, $H - 2, $cerceve);
imagerectangle($im, 3, 3, $W - 4, $H - 4, $sf ? $cerceve : $r($T['cerceve'], 60));
// Kose vidalari (dolap detayi)
$vida = $sf ? $r($T['baslik']) : $r($T['baslik'], 40);
foreach ([[7, 7], [$W - 8, 7], [7, $H - 8], [$W - 8, $H - 8]] as $v) {
imagefilledellipse($im, $v[0], $v[1], 3, 3, $vida);
}
// ---------- BASLIK SERIDI ----------
// Marquee: dolabin ustundeki isikli tabela.
// Seffaf temada opak cizilir - yoksa altindaki nobetci renk sizar.
$mSol = $PADDING;
$mSag = $W - $PADDING;
if ($tema === 'acik') {
// Acik temada tabela zeminle ayni tonda kalir, yazi koyu olur.
imagefilledrectangle($im, $mSol, 10, $mSag, 30, $r($T['golge']));
} else {
imagefilledrectangle(
$im, $mSol, 10, $mSag, 30,
($tema === 'seffaf') ? $r($T['ekran']) : $r($T['ekran'], 40)
);
}
imagerectangle($im, $mSol, 10, $mSag, 30, $r($T['cerceve'], 50));
yaz($im, $BASLIK, 8, $W / 2, 25, $r($T['marquee']), $FONT, $ttf, 2);
// ---------- CRT EKRANI ----------
$eSol = $EKRAN_SOL;
$eSag = $W - $PADDING;
$eUst = $EKRAN_UST;
$eAlt = $H - $PADDING;
imagefilledrectangle($im, $eSol, $eUst, $eSag, $eAlt, $r($T['ekran']));
// Ekran ici parlama: ustten asagi sonen ince cam yansimasi
for ($i = 0; $i < 10; $i++) {
imagefilledrectangle(
$im,
$eSol + 1, $eUst + 1 + $i,
$eSag - 1, $eUst + 1 + $i,
$r($T['siyan'], 116 + $i)
);
}
// ---------- SURE DOLDU ----------
// Ralph isini bitirmis: tuglalar yikilmis, molozun uzerinde duruyor.
if ($kalan <= 0) {
$tUstB = $eAlt - $TUGLA_YUK + 4;
// Yikilmis tugla sirasi - hepsi kirik, yalnizca moloz kalmis
$tg = 26;
for ($bx = $eSol + 2; $bx < $eSag - 2; $bx += $tg) {
$bx2 = min($bx + $tg - 2, $eSag - 2);
$yh = (int) (($TUGLA_YUK - 6) * 0.45);
imagefilledrectangle(
$im, $bx, $tUstB + ($TUGLA_YUK - 6) - $yh, $bx2, $tUstB + $TUGLA_YUK - 6,
$r($T['tuglaKen'])
);
}
yaz($im, $BITTI_METNI, 18, $W / 2, $eUst + 52, $r($T['magenta']), $FONT, $ttf, 4);
// Ralph, molozun uzerinde - yumruk asagida, is bitmis
ralphCiz(
$im, $eSag - $RALPH_G - 6, $tUstB - 12 * $RALPH_P + 2, $RALPH_P, 1.0,
[
$r($T['ten']), $r($T['tulum']), $r($T['amber']),
$r($T['mor']), $r($T['tuglaKen']),
]
);
tarama($im, $eSol, $eUst, $eSag, $eAlt, $r($T['ekran'], 100));
return $im;
}
$vals = degerler($kalan, $ADET, $gunBasamak);
// ---------- VURUS RITMI ----------
// Ralph her saniye bir kez vuruyor: kareler sirayla "yumruk
// asagida" ve "kol yukarida" diye gidiyor. Bu yuzden tek sayili
// ve cift sayili kareler farkli cizilir.
//
// Rakamlarin kendisi sabit duruyor. Vurusta onlari da
// titretmeyi denedim ama o zaman her karede butun rakam satiri
// degismis sayiliyor ve dosya boyutu ucuyor. Hareket sadece
// Ralph'ta ve tuglalarda kaliyor.
$kirik = -1;
$adim = $faz % 2; // 0 = yumruk zeminde, 1 = kol yukarida
if ($adim === 0) {
$vurusFaz = 1.0;
// Kirilan tuglayi her saniye degistiriyoruz ki Ralph hep
// ayni yere vurmasin.
$kirik = ((int) $kalan) % 9;
} else {
$vurusFaz = 0.15;
}
// ---------- RENKLER ----------
$acik = $r($T['siyan']);
$govde = $r($T['mor'], 40); // blok kenarlarindaki koyu ton
$vurgu = $r($T['amber']);
// Son 10 saniyede rakamlar pembeye doner - aciliyet hissi.
if ($kalan <= 10) {
$acik = $r($T['magenta']);
}
// ---------- RAKAMLARI YERLESTIR ----------
// Rakamlar soldan basliyor; Ralph icin sagda yer ayrilmisti,
// bu yuzden ikisi hicbir zaman cakismaz.
$x = $eSol + 10;
$ry = $RAKAM_UST;
foreach ($vals as $g => $deger) {
$n = $BASAMAKLAR[$g];
$gy = $ry;
for ($d = 0; $d < $n; $d++) {
$hx = $x + $d * ($HANE_G + $HANE_BOS);
rakamCiz($im, $deger[$d], $hx, $gy, $PIX, $acik, $govde);
}
// Etiket
$grupMerkez = $x + $grupGen[$g] / 2;
yaz($im, $ETIKETLER[$g], 7, $grupMerkez, $ETIKET_Y, $r($T['baslik']), $FONT, $ttf, 2);
$x += $grupGen[$g];
// Ayirac
if ($g < $ADET - 1) {
// Yanip sonme: vurus aninda parlak
$aRenk = ($adim === 0) ? $vurgu : $r($T['amber'], 90);
ayiracCiz($im, $x + (int) round(($GRUP_BOS - $PIX) / 2), $gy, $PIX, $aRenk, $govde);
$x += $GRUP_BOS;
}
}
// ---------- TUGLA SIRASI + RALPH ----------
$tUst = $eAlt - $TUGLA_YUK + 4;
tuglaCiz(
$im, $eSol + 2, $eSag - 2, $tUst, $TUGLA_YUK - 6, $kirik,
[$r($T['tugla']), $r($T['tuglaKen']), $r($T['amber'], 40)]
);
// Ralph rakamlarin sagindaki kendi seridinde, tuglalarin uzerinde durur
$ralphP = $RALPH_P;
$ralphX = $eSag - $RALPH_G - 6;
$ralphY = $tUst - 12 * $ralphP + 2;
ralphCiz(
$im, $ralphX, $ralphY, $ralphP, $vurusFaz,
[
$r($T['ten']),
$r($T['tulum']),
[$r($T['amber'])][0],
$r($T['mor']),
$r($T['tuglaKen']),
]
);
// ---------- CRT TARAMA CIZGILERI ----------
tarama($im, $eSol, $eUst, $eSag, $eAlt, $r($T['ekran'], 100));
return $im;
}
/** CRT tarama cizgileri - her 3 pikselde bir yatay koyu cizgi. */
function tarama($im, $sol, $ust, $sag, $alt, $renk)
{
for ($y = $ust; $y <= $alt; $y += 3) {
imageline($im, $sol + 1, $y, $sag - 1, $y, $renk);
}
}
// ==================================================================
// GIF BIRLESTIRICI — isin can alici noktasi
//
// PHP'nin GD eklentisi animasyonlu GIF uretemez; imagegif() her
// seferinde tek kareli bir GIF verir. Bu yuzden animasyonu elle
// kuruyoruz:
//
// 1) Her kareyi ayri ayri cizip GD'ye tek kareli GIF yaptiriyoruz.
// 2) O GIF'lerin icini acip sikistirilmis goruntu verisini ve
// renk paletini cekiyoruz (asagidaki gifCoz fonksiyonu).
// 3) GIF89a dosya yapisini kendimiz yazip kareleri arka arkaya
// diziyoruz (en alttaki "GIF'I KUR" bolumu).
//
// Goruntu verisini yeniden sikistirmiyoruz; GD'nin sikistirdigi
// hazir bloklari oldugu gibi tasiyoruz. Bu yuzden hizli calisir.
// ==================================================================
/**
* Tek kareli bir GIF'in icinden renk paletini ve sikistirilmis
* goruntu verisini cikarir. GIF dosyasi bloklardan olusur; basindan
* baslayip blok blok ilerliyor, isimize yarayan yere gelince duruyoruz.
*/
function gifCoz($bin)
{
$packed = ord($bin[10]);
$p = 13;
$gct = '';
$gctBit = 0;
if ($packed & 0x80) {
$gctBit = $packed & 0x07;
$len = 3 * (1 << ($gctBit + 1));
$gct = substr($bin, $p, $len);
$p += $len;
}
$son = strlen($bin);
$seffafIdx = -1;
while ($p < $son) {
$b = ord($bin[$p]);
if ($b === 0x21) { // uzanti blogu
// Graphic Control Extension ise seffaf renk indeksini al
if ($p + 1 < $son && ord($bin[$p + 1]) === 0xF9) {
$bayrak = ord($bin[$p + 3]);
if ($bayrak & 0x01) { $seffafIdx = ord($bin[$p + 6]); }
}
$p += 2;
while ($p < $son && ($sz = ord($bin[$p])) !== 0) { $p += 1 + $sz; }
$p++;
} elseif ($b === 0x2C) { // goruntu tanimlayici
$ipacked = ord($bin[$p + 9]);
$p += 10;
$pal = $gct;
$bit = $gctBit;
if ($ipacked & 0x80) {
$bit = $ipacked & 0x07;
$len = 3 * (1 << ($bit + 1));
$pal = substr($bin, $p, $len);
$p += $len;
}
$lzwMin = $bin[$p];
$p++;
$veri = '';
while ($p < $son && ($sz = ord($bin[$p])) !== 0) {
$veri .= substr($bin, $p, 1 + $sz);
$p += 1 + $sz;
}
$veri .= "\x00";
return [
'pal' => $pal,
'bit' => $bit,
'min' => $lzwMin,
'veri' => $veri,
'seffaf' => $seffafIdx,
];
} else {
break;
}
}
return null;
}
/**
* Cizilmis bir resmi GIF verisine cevirir.
*
* $renkSayisi renk sayisini kisitlar (32, 64 gibi). Az renk =
* kucuk dosya. Tasarimda zaten az renk kullandigimiz icin gozle
* gorulur bir kayip olmuyor.
*
* Sira onemli: once renkler kisitlaniyor, SONRA saydam renk
* isaretleniyor. Tersi yapilirsa saydamlik kayboluyor.
*/
function gifBin($im, $renkSayisi = 0)
{
global $tema;
if ($renkSayisi > 0) {
imagetruecolortopalette($im, false, $renkSayisi);
}
if ($tema === 'seffaf') {
// Isaret rengini palette buluyoruz. Renk kisitlama sirasinda
// ton biraz kaymis olabilecegi icin en yakin esi ariyoruz.
$idx = imagecolorclosest($im, SEFFAF_R, SEFFAF_G, SEFFAF_B);
if ($idx >= 0) {
imagecolortransparent($im, $idx);
}
}
imagesavealpha($im, true);
ob_start();
imagegif($im);
return ob_get_clean();
}
// ==================================================================
// KARELERI URET
//
// Dosya boyutunu kucuk tutan numara burada. GIF'te her kareyi tam
// boy gondermek zorunda degilsiniz; sadece degisen bolgeyi gonderip
// "geri kalani oldugu gibi birak" diyebilirsiniz.
//
// Sayacta bir saniyede degisen sey cok az: bir iki rakam, yanip
// sonen iki nokta ve Ralph. Ekranin geri kalani ayni kaliyor. Bu
// yuzden her karede yalnizca degisen kucuk dikdortgenleri yaziyoruz.
// Fark: 210 KB yerine 75 KB.
// ==================================================================
/**
* Iki kareyi karsilastirip degisen bolgeleri dikdortgen listesi
* olarak dondurur.
*
* Neden tek dikdortgen degil de liste? Cunku rakamlar solda, Ralph
* sagda duruyor. Ikisini tek bir dikdortgene sigdirsaydik aradaki
* kocaman degismemis alan da her karede bosuna gonderilirdi.
* Onun yerine degisen sutunlari gruplayip her gruba kendi
* dikdortgenini veriyoruz - GIF ayni karede birden fazla parcayi
* zaten destekliyor.
*
* @return array [[sol, ust, genislik, yukseklik], ...]
* Hicbir sey degismemisse bos dizi doner.
*/
function farkKutulari($a, $b, $w, $h)
{
// Once degisen sutunlari ve satirlari isaretle
$sutun = array_fill(0, $w, false);
$ust = $h; $alt = -1;
for ($y = 0; $y < $h; $y++) {
$satirDegisti = false;
for ($x = 0; $x < $w; $x++) {
if (imagecolorat($a, $x, $y) !== imagecolorat($b, $x, $y)) {
$sutun[$x] = true;
$satirDegisti = true;
}
}
if ($satirDegisti) {
if ($y < $ust) { $ust = $y; }
$alt = $y;
}
}
if ($alt < 0) { return []; }
// Bitisik degisen sutunlari gruplara ayir.
// Aralarinda $ESIK'ten az bos sutun varsa ayni gruba katilir - cok sayida
// minik blok, palet yuku yuzunden tek bloktan pahali olurdu.
$ESIK = 24;
$gruplar = [];
$bas = -1;
$bosluk = 0;
for ($x = 0; $x < $w; $x++) {
if ($sutun[$x]) {
if ($bas < 0) { $bas = $x; }
$son = $x;
$bosluk = 0;
} elseif ($bas >= 0) {
$bosluk++;
if ($bosluk > $ESIK) {
$gruplar[] = [$bas, $son];
$bas = -1;
}
}
}
if ($bas >= 0) { $gruplar[] = [$bas, $son]; }
// Her grup icin dikey sinirlari daralt
$kutular = [];
foreach ($gruplar as $g) {
list($gs, $gson) = $g;
$gUst = $h; $gAlt = -1;
for ($y = $ust; $y <= $alt; $y++) {
for ($x = $gs; $x <= $gson; $x++) {
if (imagecolorat($a, $x, $y) !== imagecolorat($b, $x, $y)) {
if ($y < $gUst) { $gUst = $y; }
$gAlt = $y;
break;
}
}
}
if ($gAlt < 0) { continue; }
$kutular[] = [$gs, $gUst, $gson - $gs + 1, $gAlt - $gUst + 1];
}
return $kutular;
}
// Uretilen parcalar burada birikiyor. Her eleman:
// [sol, ust, goruntuVerisi, kareninSonuMu]
$kareler = [];
$oncekiIm = null; // karsilastirma icin bir onceki kare
$oncekiBitti = null;
// Saniye saniye ilerleyip her an icin bir kare ciziyoruz.
for ($i = 0; $i < $KARE_SAYISI; $i++) {
$kalan = $hedefTs - ($simdi + $i);
$bitti = ($kalan <= 0);
// Sure dolduktan sonra ekran hic degismiyor; tek bir
// "OYUN BITTI" karesi yeter, gerisini uretmiyoruz.
if ($bitti && $oncekiBitti) { continue; }
$im = kareCiz($kalan, $i);
if ($oncekiIm === null) {
// Ilk kare tam boy gonderilir - uzerine eklenecek bir sey
// olmadigi icin zemin gorevi gorur.
//
// Kucuk bir tuzak: gifBin() resmin renklerini yerinde
// degistirdigi icin, karsilastirmada kullanacagimiz temiz
// kopyayi onceden almamiz gerekiyor. Yoksa bir sonraki
// kareyle karsilastirdigimizda her piksel farkli cikardi.
$kopya = imagecreatetruecolor($W, $H);
imagealphablending($kopya, false);
imagesavealpha($kopya, true);
imagecopy($kopya, $im, 0, 0, 0, 0, $W, $H);
$kareler[] = [0, 0, gifBin($im, 128), true];
imagedestroy($im);
$im = $kopya;
} else {
$kutular = farkKutulari($oncekiIm, $im, $W, $H);
if (!$kutular) {
// Hicbir sey degismedi - kareyi atla
imagedestroy($im);
continue;
}
$sonIdx = count($kutular) - 1;
foreach ($kutular as $ki => $kutu) {
list($sol, $ust, $gen, $yuk) = $kutu;
$kirp = imagecreatetruecolor($gen, $yuk);
imagealphablending($kirp, false);
imagesavealpha($kirp, true);
imagecopy($kirp, $im, 0, 0, $sol, $ust, $gen, $yuk);
// Yalnizca son blok gecikme tasir; oncekiler ayni anda cizilir.
$kareler[] = [$sol, $ust, gifBin($kirp, 32), $ki === $sonIdx];
imagedestroy($kirp);
}
}
if ($oncekiIm !== null) { imagedestroy($oncekiIm); }
$oncekiIm = $im;
$oncekiBitti = $bitti;
}
if ($oncekiIm !== null) { imagedestroy($oncekiIm); }
// ==================================================================
// GIF'I KUR
//
// Elimizde kare kare goruntu parcalari var; simdi bunlari GIF89a
// dosya yapisina gore arka arkaya diziyoruz. Sira sabittir:
// basik bilgi -> renk paleti -> dongu ayari -> kareler -> bitis
// ==================================================================
$ilk = gifCoz($kareler[0][2]);
// --- Dosya basligi: imza, olculer, renk paleti ---
$out = "GIF89a";
$out .= pack('v', $W) . pack('v', $H);
$out .= chr(0x80 | 0x70 | $ilk['bit']) . "\x00\x00";
$out .= $ilk['pal'];
// --- Dongu ayari ---
// Tarayicilara animasyonun kac kez oynayacagini soyleyen blok.
// ($DONGU 1 ise bir kez oynar, 0 ise sonsuz doner.)
$out .= "\x21\xFF\x0B" . "NETSCAPE2.0" . "\x03\x01" . pack('v', $DONGU) . "\x00";
// --- Kareler ---
foreach ($kareler as $idx => $k) {
list($sol, $ust, $bin, $gecikmeVer) = $k;
$f = gifCoz($bin);
if ($f === null) { continue; }
// Her parcanin onune bir kontrol blogu koyuyoruz. Burada iki
// sey soyluyoruz: bu parca ne kadar ekranda kalacak ve
// silinmeden yerinde birakilacak (silinseydi altindaki kare
// kaybolur, ekran yanip sonerdi).
//
// Bir kareyi birden fazla parcaya bolmustuk. O parcalarin
// yalnizca sonuncusuna bekleme suresi veriyoruz; oncekiler
// 0 sureyle ard arda cizildigi icin izleyici tek kare gorur.
$bayrak = (1 << 2);
$seffafIdx = 0;
if ($f['seffaf'] >= 0) {
$bayrak |= 0x01;
$seffafIdx = $f['seffaf'];
}
$out .= "\x21\xF9\x04" . chr($bayrak)
. pack('v', $gecikmeVer ? $KARE_SURESI : 0)
. chr($seffafIdx) . "\x00";
// Parcanin ekranda nereye oturacagini ve olculerini yaziyoruz.
// Tam boy olmadigi icin konum bilgisi onemli: tarayici bu
// parcayi onceki karenin uzerine, tam bu noktaya cizecek.
$gen = ord($bin[6]) | (ord($bin[7]) << 8);
$yuk = ord($bin[8]) | (ord($bin[9]) << 8);
$out .= "\x2C" . pack('v', $sol) . pack('v', $ust) . pack('v', $gen) . pack('v', $yuk);
// Parcanin kendi renk paleti ve sikistirilmis goruntu verisi.
$out .= chr(0x80 | $f['bit']) . $f['pal'];
$out .= $f['min'] . $f['veri'];
}
// --- Dosya sonu isareti ---
$out .= "\x3B";
// ==================================================================
// ONBELLEGE YAZ
// ==================================================================
if ($onbellekDosya !== null) {
// Once gecici bir dosyaya yazip sonra asil adina tasiyoruz.
// Boylece ayni anda gelen baska bir istek, yarim yazilmis
// dosyayi okuyup bozuk resim gostermez.
$gecici = $onbellekDosya . '.' . getmypid() . '.tmp';
if (@file_put_contents($gecici, $out) === strlen($out)) {
if (!@rename($gecici, $onbellekDosya)) { @unlink($gecici); }
} else {
// Yazamadiysak (izin yok, disk dolu) sorun degil; resim
// yine gonderilir, sadece onbellekten yararlanilmaz.
@unlink($gecici);
}
// Ayarlari her degistirdiginizde yeni bir dosya adi olusur ve
// eskisi oldugu yerde kalir. Klasor sismesin diye ara sira
// (yaklasik her 50 istekte bir) 1 saatten eski dosyalari
// siliyoruz. Her istekte yapmak gereksiz yere disk yorardi.
if (mt_rand(1, 50) === 1) {
foreach ((array) @glob($ONBELLEK_DIZ . '/*.gif') as $eski) {
if (time() - (int) @filemtime($eski) > 3600) { @unlink($eski); }
}
}
}
// ==================================================================
// CIKTI
// ==================================================================
gifGonder($out);
o yüzden seçmedim

<?php
/**
* ARCADE GERI SAYIM SAYACI — animasyonlu GIF
* ---------------------------------------------------------------
*
* NEDEN GIF?
* PNG durgun bir resimdir; sayfa yenilenmeden degismez. GIF ise
* animasyon tasiyabildigi icin, sunucu istek geldigi anda 60
* saniyelik bir animasyon uretir ve tarayici bunu kendi oynatir.
* Sonuc: sayac sayfa yenilenmeden tiklar. Forumlar <img> etiketine
* izin verdigi icin de konu icinde, imzada, her yerde calisir.
*
* KULLANIM
* HTML : <img src="https://siteniz.com/index.php">
* BBCode : [img]https://siteniz.com/index.php[/img]
*
* TEMALAR (adresin sonuna eklenir)
* ?tema=koyu koyu zemin (varsayilan)
* ?tema=acik acik zemin
* ?tema=seffaf zemin saydam; forum kendi arka planini gosterir,
* yani hem koyu hem acik temali forumda uyar
*
* KURULUM
* 1) Bu dosyayi sunucuya atin.
* 2) Yanina "onbellek" adinda bos bir klasor acip yazma izni
* verin (755). Acmazsaniz yine calisir, sadece her istekte
* bastan uretir - yani daha yavas olur.
* 3) Turkce karakter (GUN yerine GÜN) isterseniz ayni klasore
* "font.ttf" atin. Rakamlar fonta ihtiyac duymaz, onlari kod
* kendi cizer; font yalnizca alt etiketler icindir.
*
* GEREKSINIM
* PHP + GD eklentisi (imagegif). Baska kutuphane yok.
*
* NOT
* Animasyon 60 saniyeliktir; sonunda son karede durur. Sayfa
* yenilendiginde bastan uretilir. Daha uzun sursun isterseniz
* asagidaki $KARE_SAYISI degerini buyutun (dosya boyutu artar).
*/
// ==================================================================
// AYARLAR — duzenlemeniz gereken tek bolum burasi
// ==================================================================
date_default_timezone_set('Europe/Istanbul');
// Geri sayimin bitecegi an. Bicim: 'YIL-AY-GUN SAAT:DAKIKA:SANIYE'
$HEDEF = '2026-09-15 14:13:00';
// Sayacin ustundeki tabelada yazan metin
$BASLIK = 'UGILABS - GIF Versiyon';
// Sure doldugunda ekranda yazacak metin
$BITTI_METNI = 'OYUN BITTI';
// ------------------------------------------------------------------
// ANIMASYON
// ------------------------------------------------------------------
// Kac saniyelik animasyon uretilsin. 60 = 1 dakika.
// Buyutursen daha uzun sure tiklar ama dosya buyur:
// 60 kare ~75 KB (1 dakika)
// 300 kare ~250 KB (5 dakika)
$KARE_SAYISI = 60;
// Her karenin ekranda kalma suresi (1/100 saniye cinsinden).
// 100 = 1 saniye. Sayac saniye saydigi icin bunu degistirme.
$KARE_SURESI = 100;
// 1 = bir kez oynar, sonra son karede durur.
// 0 = sonsuz doner. Surekli hareket eder ama her turda basa
// saracagi icin yanlis zaman gostermeye baslar. Sayac icin
// 1 dogrusu; sonsuz dongu ancak susleme amacli isine yarar.
$DONGU = 1;
// false yaparsan saniye hanesi kaybolur, sadece gun/saat/dakika
// kalir. O durumda animasyona gerek kalmaz, tek kare uretilir.
$SANIYE_GOSTER = true;
// ------------------------------------------------------------------
// ONBELLEK — paylasimli hostingde sunucuyu ayakta tutar
//
// Her istekte animasyonu bastan cizmek sunucuyu yorar (~0.8 sn).
// Populer bir konuda 50 kisi ayni anda sayfayi acarsa bu 40 saniye
// islemci demektir ve paylasimli hosting bunu kaldirmaz.
//
// Bu yuzden uretilen GIF diske yazilir; asagidaki sure boyunca
// gelen istekler yeniden cizmeden hazir dosyayi alir. 50 kisi ayni
// anda girse bile tek uretim yapilir.
//
// Sayacin dogrulugunu bozmaz: en fazla bu sure kadar geriden gelir,
// yani 3 saniyede kimsenin fark edecegi bir sapma olusmaz.
// ------------------------------------------------------------------
// Uretilen dosya kac saniye saklansin. 0 yazarsan onbellek kapanir.
$ONBELLEK_SN = 3;
// Dosyalarin yazilacagi klasor. Yazma izni yoksa sistem yine
// calisir, sadece her istekte bastan uretir.
$ONBELLEK_DIZ = __DIR__ . '/onbellek';
// ------------------------------------------------------------------
// FONT
// Rakamlar fonta ihtiyac duymaz - onlari kod nokta nokta cizer.
// Buradaki font yalnizca alt etiketler (GUN, SAAT...) ve baslik
// icindir. Turkce karakter isterseniz klasore font.ttf atin;
// bulunamazsa asagidaki sistem fontlari sirayla denenir.
// ------------------------------------------------------------------
$FONT_ADAYLARI = [
__DIR__ . '/font.ttf',
'C:/Windows/Fonts/consolab.ttf',
'/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',
'/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',
];
// ------------------------------------------------------------------
// TEMA — arcade dolabinin ekrani
// ------------------------------------------------------------------
$tema = isset($_GET['tema']) ? strtolower($_GET['tema']) : 'koyu';
if (!in_array($tema, ['koyu', 'acik', 'seffaf'], true)) { $tema = 'koyu'; }
$T = [
// Ortak arcade paleti - zeminden bagimsiz, her iki temada da okunur.
'siyan' => [ 34, 224, 232],
'magenta' => [255, 61, 139],
'amber' => [255, 197, 61],
'mor' => [ 27, 16, 53],
'ten' => [217, 142, 95],
'tulum' => [ 46, 92, 138],
'tugla' => [166, 74, 58],
'tuglaKen' => [ 92, 38, 30],
];
// 'ekran' her temada koyu kalir - CRT camı zeminden bagimsizdir.
// 'baslik' : ekranin ICINDEKI etiketler (hep koyu zemin uzerinde)
// 'marquee' : ekranin USTUNDEKI tabela yazisi (zemine gore degisir)
if ($tema === 'acik') {
$T['zemin'] = [232, 230, 240];
$T['ekran'] = [ 38, 24, 70];
$T['cerceve'] = [ 62, 44, 104];
$T['baslik'] = [166, 152, 208];
$T['marquee'] = [ 44, 30, 84]; // acik zeminde koyu yazi
$T['golge'] = [206, 202, 220];
} else {
$T['zemin'] = [ 16, 10, 30];
$T['ekran'] = [ 30, 18, 58];
$T['cerceve'] = [ 62, 44, 104];
$T['baslik'] = [148, 132, 196];
$T['marquee'] = [172, 156, 216];
$T['golge'] = [ 10, 6, 20];
}
// ==================================================================
// ONBELLEKTEN SUNMA
//
// Bu bolum dosyanin en basinda duruyor cunku amaci cizim islerine
// hic girmeden isi bitirmek. Yeterince taze bir dosya varsa onu
// gonderip cikiyoruz; asagidaki kodun tamami hic calismiyor.
// ==================================================================
/** Hazir GIF verisini tarayiciya gonderir ve betigi sonlandirir. */
function gifGonder($veri)
{
header('Content-Type: image/gif');
header('Content-Length: ' . strlen($veri));
// Tarayiciya "bu resmi saklama" diyoruz. Saklarsa sayfa
// yenilendiginde eski animasyonu gosterir ve sayac yanlis
// zamanda kalir.
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Expires: Sat, 01 Jan 2000 00:00:00 GMT');
echo $veri;
exit;
}
$onbellekDosya = null;
if ($ONBELLEK_SN > 0) {
// Dosya adini ayarlardan uretiyoruz: ayni ayarlar ayni dosyayi
// kullanir, farkli tema kendi dosyasini alir. Boylece temalar
// birbirine karismaz.
$anahtar = md5(implode('|', [
$HEDEF, $BASLIK, $BITTI_METNI, $tema,
$KARE_SAYISI, $KARE_SURESI, $DONGU, $SANIYE_GOSTER,
]));
if (!is_dir($ONBELLEK_DIZ)) { @mkdir($ONBELLEK_DIZ, 0755, true); }
$onbellekDosya = $ONBELLEK_DIZ . '/' . $anahtar . '.gif';
// Dosya var ve yeterince taze mi? Oyleyse hic cizmeden gonder.
if (is_readable($onbellekDosya)) {
$yas = time() - (int) @filemtime($onbellekDosya);
if ($yas >= 0 && $yas < $ONBELLEK_SN) {
$hazir = @file_get_contents($onbellekDosya);
if ($hazir !== false && strlen($hazir) > 0) {
gifGonder($hazir);
}
}
}
// Buraya dustuyse dosya yok ya da eskimis; asagida yeniden
// uretilip en sonda diske yazilacak.
}
// ==================================================================
// HAZIRLIK
// ==================================================================
$FONT = null;
foreach ($FONT_ADAYLARI as $f) {
if (is_readable($f)) { $FONT = $f; break; }
}
$ttf = ($FONT !== null);
$hedefTs = strtotime($HEDEF);
$simdi = time();
if (!$SANIYE_GOSTER) { $KARE_SAYISI = 1; }
$ETIKETLER = ['GUN', 'SAAT', 'DAKIKA', 'SANIYE'];
$ADET = $SANIYE_GOSTER ? 4 : 3;
// ------------------------------------------------------------------
// OLCULER
// Ekran bir arcade dolabinin CRT'si: disinda cerceve, icinde rakamlar,
// en altta Ralph'in yumruk attigi tugla siras var.
// ------------------------------------------------------------------
$PADDING = 14; // dis cerceve kalinligi
// Rakamlar 5x7 glif izgarasi. $PIX bir glif pikselinin blok boyutu.
$PIX = 6;
$HANE_G = 5 * $PIX; // bir rakam hanesinin genisligi
$RAKAM_YUK = 7 * $PIX; // glif yuksekligi
$HANE_BOS = $PIX; // ayni grup icindeki haneler arasi
$GRUP_BOS = $PIX * 3; // gruplar arasi (iki nokta buraya gelir)
// Gun hanesi 3 basamak olabilir (999 gun), digerleri hep 2 basamak.
$gunBasamak = max(2, strlen((string) (int) floor(max(0, $hedefTs - $simdi) / 86400)));
$BASAMAKLAR = [$gunBasamak, 2, 2, 2];
$BASAMAKLAR = array_slice($BASAMAKLAR, 0, $ADET);
$grupGen = [];
foreach ($BASAMAKLAR as $n) {
$grupGen[] = $n * $HANE_G + ($n - 1) * $HANE_BOS;
}
// Rakam bloklarinin toplam genisligi
$RAKAM_G = array_sum($grupGen) + ($ADET - 1) * $GRUP_BOS;
// Ralph rakamlarin SAGINDA kendi seridinde durur - etiketleri kapatmaz.
$RALPH_P = 3; // Ralph'in piksel blok boyutu
$RALPH_G = 13 * $RALPH_P; // Ralph + yumruk icin ayrilan genislik
$RALPH_BOS = 14; // rakamlarla Ralph arasi bosluk
$EKRAN_G = $RAKAM_G + $RALPH_BOS + $RALPH_G + 16;
$EKRAN_SOL = $PADDING;
$EKRAN_UST = 42; // ustte baslik seridi
$EKRAN_YUK = 92; // rakamlar + etiketler
$TUGLA_YUK = 26; // Ralph'in kirdigi tugla sirasi
$W = $EKRAN_G + $PADDING * 2;
$H = $EKRAN_UST + $EKRAN_YUK + $TUGLA_YUK + $PADDING;
// Rakamlarin dikey konumu ($RAKAM_YUK yukarida glif izgarasindan geldi)
$RAKAM_UST = $EKRAN_UST + 16;
$ETIKET_Y = $RAKAM_UST + $RAKAM_YUK + 16;
// ------------------------------------------------------------------
// SEFFAF TEMA ICIN ISARET RENGI
//
// GIF'te saydamlik, "su renk saydam sayilsin" diye tek bir rengi
// isaretleyerek olur. Dogrudan saydam cizemeyiz, cunku GIF'e
// cevirirken kullanilan imagetruecolortopalette() saydamlik
// bilgisini korumaz.
//
// Bu yuzden zemini once bu renkle boyuyoruz - tasarimda hic
// gecmeyen, kimseye benzemeyen parlak bir ton. GIF'e yazarken de
// "bu renk saydamdir" diye isaretliyoruz. Sonuc: forumun kendi
// arka plani resmin altindan gorunuyor.
// ------------------------------------------------------------------
define('SEFFAF_R', 255);
define('SEFFAF_G', 0);
define('SEFFAF_B', 254);
// ==================================================================
// ARCADE PIKSEL FONT
// Rakamlar 5x7 izgaraya cizilmis gercek bitmap glifler. Her "piksel"
// ekranda $p x $p'lik bir blok olur - klasik arcade skor tabelasi gibi.
// TTF'e bagli degil, her sunucuda birebir ayni gorunur.
// ==================================================================
/** 5x7 rakam glifleri. Her satir 5 karakter: '#' dolu, '.' bos. */
function glif($ch)
{
static $font = [
'0' => ['.###.',
'#...#',
'#..##',
'#.#.#',
'##..#',
'#...#',
'.###.'],
'1' => ['..#..',
'.##..',
'..#..',
'..#..',
'..#..',
'..#..',
'.###.'],
'2' => ['.###.',
'#...#',
'....#',
'...#.',
'..#..',
'.#...',
'#####'],
'3' => ['#####',
'...#.',
'..#..',
'...#.',
'....#',
'#...#',
'.###.'],
'4' => ['...#.',
'..##.',
'.#.#.',
'#..#.',
'#####',
'...#.',
'...#.'],
'5' => ['#####',
'#....',
'####.',
'....#',
'....#',
'#...#',
'.###.'],
'6' => ['..##.',
'.#...',
'#....',
'####.',
'#...#',
'#...#',
'.###.'],
'7' => ['#####',
'....#',
'...#.',
'..#..',
'.#...',
'.#...',
'.#...'],
'8' => ['.###.',
'#...#',
'#...#',
'.###.',
'#...#',
'#...#',
'.###.'],
'9' => ['.###.',
'#...#',
'#...#',
'.####',
'....#',
'...#.',
'.##..'],
];
return isset($font[$ch]) ? $font[$ch] : null;
}
/**
* Tek bir rakami piksel bloklariyla cizer.
*
* $p : bir glif pikselinin ekrandaki blok boyutu
* $acik : dolu piksel rengi
* $govde : blogun ust kenarinda kullanilan koyu ton (hacim hissi)
*/
function rakamCiz($im, $rakam, $x, $y, $p, $acik, $govde)
{
$g = glif($rakam);
if ($g === null) { return; }
foreach ($g as $sy => $satir) {
for ($sx = 0; $sx < 5; $sx++) {
if ($satir[$sx] !== '#') { continue; }
$x1 = $x + $sx * $p;
$y1 = $y + $sy * $p;
$x2 = $x1 + $p - 1;
$y2 = $y1 + $p - 1;
imagefilledrectangle($im, $x1, $y1, $x2, $y2, $acik);
// Her blogun alt-sag kenarinda bir tik koyu ton: pikseller
// birbirine yapismaz, izgara okunur kalir.
imageline($im, $x1, $y2, $x2, $y2, $govde);
imageline($im, $x2, $y1, $x2, $y2, $govde);
}
}
}
/** Iki nokta ayirac - glif izgarasiyla ayni blok boyutunda. */
function ayiracCiz($im, $x, $y, $p, $renk, $govde)
{
foreach ([1, 4] as $sy) {
$y1 = $y + $sy * $p + (int) round($p / 2);
imagefilledrectangle($im, $x, $y1, $x + $p - 1, $y1 + $p - 1, $renk);
imageline($im, $x, $y1 + $p - 1, $x + $p - 1, $y1 + $p - 1, $govde);
}
}
// ==================================================================
// CIZIM YARDIMCILARI
// ==================================================================
/** Yatayda ortalanmis metin. Harf araligi (tracking) destekli. */
function yaz($im, $metin, $boyut, $merkezX, $baselineY, $renk, $font, $ttf, $aralik = 0)
{
if ($ttf && $aralik > 0) {
// Harf harf cizip aralik veriyoruz - arcade etiketleri genis olur.
$harfler = preg_split('//u', $metin, -1, PREG_SPLIT_NO_EMPTY);
$toplam = 0;
$genler = [];
foreach ($harfler as $h) {
$bb = imagettfbbox($boyut, 0, $font, $h);
$g = $bb[2] - $bb[0];
$genler[] = $g;
$toplam += $g + $aralik;
}
$toplam -= $aralik;
$x = (int) ($merkezX - $toplam / 2);
foreach ($harfler as $i => $h) {
imagettftext($im, $boyut, 0, $x, $baselineY, $renk, $font, $h);
$x += $genler[$i] + $aralik;
}
return;
}
if ($ttf) {
$bbox = imagettfbbox($boyut, 0, $font, $metin);
$x = (int) ($merkezX - ($bbox[2] - $bbox[0]) / 2);
imagettftext($im, $boyut, 0, $x, $baselineY, $renk, $font, $metin);
} else {
$f = $boyut >= 18 ? 5 : ($boyut >= 11 ? 3 : 2);
// Dahili fontta aralik icin harf harf cizim
if ($aralik > 0) {
$bg = imagefontwidth($f);
$toplam = strlen($metin) * ($bg + $aralik) - $aralik;
$x = (int) ($merkezX - $toplam / 2);
$y = (int) ($baselineY - imagefontheight($f));
for ($i = 0; $i < strlen($metin); $i++) {
imagestring($im, $f, $x, $y, $metin[$i], $renk);
$x += $bg + $aralik;
}
return;
}
$x = (int) ($merkezX - imagefontwidth($f) * strlen($metin) / 2);
$y = (int) ($baselineY - imagefontheight($f));
imagestring($im, $f, $x, $y, $metin, $renk);
}
}
/** Belirli bir zaman icin hane metinlerini dondurur. */
function degerler($kalan, $adet, $gunBasamak)
{
if ($kalan < 0) { $kalan = 0; }
$v = [
str_pad((string) (int) floor($kalan / 86400), $gunBasamak, '0', STR_PAD_LEFT),
str_pad((string) (int) floor(($kalan % 86400) / 3600), 2, '0', STR_PAD_LEFT),
str_pad((string) (int) floor(($kalan % 3600) / 60), 2, '0', STR_PAD_LEFT),
str_pad((string) (int) ($kalan % 60), 2, '0', STR_PAD_LEFT),
];
return array_slice($v, 0, $adet);
}
// ------------------------------------------------------------------
// RALPH
//
// Karakteri hazir bir resimden almiyoruz, blok blok ciziyoruz.
// Kocaman yumruklar, mavi tulum, dagink portakal sac.
//
// Asagidaki $b() yardimcisi isi kolaylastiriyor: koordinatlari
// piksel yerine kucuk kareler halinde veriyoruz, o da $p ile
// carpip ekrana ciziyor. Yani $b(3, 2, 5, 3) demek "soldan 3.,
// yukaridan 2. kareden basla, 5 kare genis 3 kare yuksek bir
// dikdortgen doldur" demek.
// ------------------------------------------------------------------
/**
* Ralph'i cizer.
*
* $vurus yumrugun nerede oldugunu belirler:
* 0 = kol yukarida (hazirlaniyor)
* 1 = yumruk asagida (tuglaya indi)
* Arasindaki degerler ara konumlari verir.
*/
function ralphCiz($im, $x, $y, $p, $vurus, $renkler)
{
list($ten, $tulum, $sac, $koyu, $ayakkabi) = $renkler;
$b = function ($gx, $gy, $gw, $gh, $renk) use ($im, $x, $y, $p) {
imagefilledrectangle(
$im,
$x + $gx * $p,
$y + $gy * $p,
$x + ($gx + $gw) * $p - 1,
$y + ($gy + $gh) * $p - 1,
$renk
);
};
// Vurusta govde one dogru cokuyor
$eg = (int) round($vurus);
// --- Sac (portakal, dagink) ---
$b(3, 0 + $eg, 5, 1, $sac);
$b(2, 1 + $eg, 7, 1, $sac);
$b(2, 0 + $eg, 1, 1, $sac); // dagink tutamlar
$b(8, 0 + $eg, 1, 1, $sac);
// --- Yuz ---
$b(3, 2 + $eg, 5, 3, $ten);
$b(3, 3 + $eg, 1, 1, $koyu); // sol goz
$b(6, 3 + $eg, 1, 1, $koyu); // sag goz
$b(4, 4 + $eg, 3, 1, $koyu); // agiz - kararli ifade
// --- Govde (tulum) ---
$b(3, 5 + $eg, 5, 4, $tulum);
$b(4, 5 + $eg, 1, 1, $koyu); // tulum askilari
$b(6, 5 + $eg, 1, 1, $koyu);
// --- Sol kol + dev yumruk (sabit, yanda asili) ---
$b(2, 6 + $eg, 1, 2, $ten);
$b(0, 8 + $eg, 3, 3, $ten); // Ralph'in meshur kocaman eli
$b(0, 8 + $eg, 3, 1, $koyu);
// --- Sag kol + dev yumruk: vurusa gore yukaridan tuglalara iner ---
// $vurus 0 -> yumruk basin uzerinde, 1 -> yumruk zeminde
$yumrukY = (int) round(-2 + $vurus * 11);
// Omuzdan yumruga uzanan kol
$omuzY = 6 + $eg;
$ust = min($omuzY, $yumrukY + $eg);
$alt = max($omuzY, $yumrukY + $eg + 2);
$b(8, $ust, 1, max(1, $alt - $ust), $ten);
// Yumruk (3x3 blok - govdeden buyuk, karakterin imzasi)
$b(8, $yumrukY + $eg, 3, 3, $ten);
$b(8, $yumrukY + $eg, 3, 1, $koyu); // parmak eklemleri golgesi
// --- Bacaklar ---
$b(3, 9 + $eg, 2, 2, $tulum);
$b(6, 9 + $eg, 2, 2, $tulum);
$b(2, 11 + $eg, 3, 1, $ayakkabi);
$b(6, 11 + $eg, 3, 1, $ayakkabi);
}
/**
* Tugla sirasi. $kirikIndex >= 0 ise o tugla parcalanmis cizilir.
*/
function tuglaCiz($im, $sol, $sag, $ust, $yuk, $kirikIndex, $renkler)
{
list($tugla, $ken, $toz) = $renkler;
$tg = 26; // tugla genisligi
$adet = (int) ceil(($sag - $sol) / $tg);
for ($i = 0; $i < $adet; $i++) {
$x1 = $sol + $i * $tg;
$x2 = min($x1 + $tg - 2, $sag);
if ($x1 >= $sag) { break; }
if ($i === $kirikIndex) {
// Parcalanmis tugla: ust yarisi ucmus, altta molozlar
imagefilledrectangle($im, $x1, $ust + (int) ($yuk * 0.55), $x2, $ust + $yuk, $ken);
// Ucusan parcalar
imagefilledrectangle($im, $x1 + 3, $ust - 5, $x1 + 7, $ust - 1, $toz);
imagefilledrectangle($im, $x1 + 13, $ust - 8, $x1 + 16, $ust - 5, $toz);
imagefilledrectangle($im, $x2 - 5, $ust - 3, $x2 - 2, $ust, $toz);
continue;
}
imagefilledrectangle($im, $x1, $ust, $x2, $ust + $yuk, $tugla);
imagerectangle($im, $x1, $ust, $x2, $ust + $yuk, $ken);
// Ust kenar isigi
imageline($im, $x1 + 1, $ust + 1, $x2 - 1, $ust + 1, $ken);
}
}
// ==================================================================
// KARE CIZIMI
// ==================================================================
/**
* Tek bir kareyi cizer.
*
* @param int $kalan Kalan saniye
* @param int $faz Animasyon fazi (0..N) - yumruk ve titresim icin
*/
function kareCiz($kalan, $faz)
{
global $W, $H, $ADET, $PADDING, $PIX, $HANE_G, $HANE_BOS, $GRUP_BOS, $BASAMAKLAR,
$EKRAN_SOL, $EKRAN_UST, $EKRAN_G, $EKRAN_YUK, $TUGLA_YUK,
$RAKAM_UST, $RAKAM_YUK, $ETIKET_Y, $grupGen,
$RALPH_P, $RALPH_G, $RALPH_BOS,
$BASLIK, $BITTI_METNI, $ETIKETLER, $FONT, $ttf, $T, $tema, $gunBasamak;
$im = imagecreatetruecolor($W, $H);
imagealphablending($im, false);
imagesavealpha($im, true);
$r = function ($c, $a = 0) use ($im) {
return imagecolorallocatealpha($im, $c[0], $c[1], $c[2], $a);
};
// ---------- ZEMIN ----------
// Seffaf temada zemini isaret rengiyle boyuyoruz; o renk daha
// sonra GIF'e yazilirken saydam olarak isaretleniyor.
// (Ayrintili aciklama icin yukaridaki SEFFAF_R tanimina bakin.)
if ($tema === 'seffaf') {
imagefilledrectangle(
$im, 0, 0, $W - 1, $H - 1,
imagecolorallocate($im, SEFFAF_R, SEFFAF_G, SEFFAF_B)
);
} else {
imagefilledrectangle($im, 0, 0, $W - 1, $H - 1, $r($T['zemin']));
}
imagealphablending($im, true);
// ---------- DOLAP CERCEVESI ----------
// Ekranin cevresinde ince cift cizgi - arcade dolabinin bezel'i.
// Seffaf temada yari saydam tonlar nobetci rengi karistiracagi icin
// tum cerceve ogeleri opak cizilir.
$sf = ($tema === 'seffaf');
$cerceve = $r($T['cerceve']);
imagerectangle($im, 1, 1, $W - 2, $H - 2, $cerceve);
imagerectangle($im, 3, 3, $W - 4, $H - 4, $sf ? $cerceve : $r($T['cerceve'], 60));
// Kose vidalari (dolap detayi)
$vida = $sf ? $r($T['baslik']) : $r($T['baslik'], 40);
foreach ([[7, 7], [$W - 8, 7], [7, $H - 8], [$W - 8, $H - 8]] as $v) {
imagefilledellipse($im, $v[0], $v[1], 3, 3, $vida);
}
// ---------- BASLIK SERIDI ----------
// Marquee: dolabin ustundeki isikli tabela.
// Seffaf temada opak cizilir - yoksa altindaki nobetci renk sizar.
$mSol = $PADDING;
$mSag = $W - $PADDING;
if ($tema === 'acik') {
// Acik temada tabela zeminle ayni tonda kalir, yazi koyu olur.
imagefilledrectangle($im, $mSol, 10, $mSag, 30, $r($T['golge']));
} else {
imagefilledrectangle(
$im, $mSol, 10, $mSag, 30,
($tema === 'seffaf') ? $r($T['ekran']) : $r($T['ekran'], 40)
);
}
imagerectangle($im, $mSol, 10, $mSag, 30, $r($T['cerceve'], 50));
yaz($im, $BASLIK, 8, $W / 2, 25, $r($T['marquee']), $FONT, $ttf, 2);
// ---------- CRT EKRANI ----------
$eSol = $EKRAN_SOL;
$eSag = $W - $PADDING;
$eUst = $EKRAN_UST;
$eAlt = $H - $PADDING;
imagefilledrectangle($im, $eSol, $eUst, $eSag, $eAlt, $r($T['ekran']));
// Ekran ici parlama: ustten asagi sonen ince cam yansimasi
for ($i = 0; $i < 10; $i++) {
imagefilledrectangle(
$im,
$eSol + 1, $eUst + 1 + $i,
$eSag - 1, $eUst + 1 + $i,
$r($T['siyan'], 116 + $i)
);
}
// ---------- SURE DOLDU ----------
// Ralph isini bitirmis: tuglalar yikilmis, molozun uzerinde duruyor.
if ($kalan <= 0) {
$tUstB = $eAlt - $TUGLA_YUK + 4;
// Yikilmis tugla sirasi - hepsi kirik, yalnizca moloz kalmis
$tg = 26;
for ($bx = $eSol + 2; $bx < $eSag - 2; $bx += $tg) {
$bx2 = min($bx + $tg - 2, $eSag - 2);
$yh = (int) (($TUGLA_YUK - 6) * 0.45);
imagefilledrectangle(
$im, $bx, $tUstB + ($TUGLA_YUK - 6) - $yh, $bx2, $tUstB + $TUGLA_YUK - 6,
$r($T['tuglaKen'])
);
}
yaz($im, $BITTI_METNI, 18, $W / 2, $eUst + 52, $r($T['magenta']), $FONT, $ttf, 4);
// Ralph, molozun uzerinde - yumruk asagida, is bitmis
ralphCiz(
$im, $eSag - $RALPH_G - 6, $tUstB - 12 * $RALPH_P + 2, $RALPH_P, 1.0,
[
$r($T['ten']), $r($T['tulum']), $r($T['amber']),
$r($T['mor']), $r($T['tuglaKen']),
]
);
tarama($im, $eSol, $eUst, $eSag, $eAlt, $r($T['ekran'], 100));
return $im;
}
$vals = degerler($kalan, $ADET, $gunBasamak);
// ---------- VURUS RITMI ----------
// Ralph her saniye bir kez vuruyor: kareler sirayla "yumruk
// asagida" ve "kol yukarida" diye gidiyor. Bu yuzden tek sayili
// ve cift sayili kareler farkli cizilir.
//
// Rakamlarin kendisi sabit duruyor. Vurusta onlari da
// titretmeyi denedim ama o zaman her karede butun rakam satiri
// degismis sayiliyor ve dosya boyutu ucuyor. Hareket sadece
// Ralph'ta ve tuglalarda kaliyor.
$kirik = -1;
$adim = $faz % 2; // 0 = yumruk zeminde, 1 = kol yukarida
if ($adim === 0) {
$vurusFaz = 1.0;
// Kirilan tuglayi her saniye degistiriyoruz ki Ralph hep
// ayni yere vurmasin.
$kirik = ((int) $kalan) % 9;
} else {
$vurusFaz = 0.15;
}
// ---------- RENKLER ----------
$acik = $r($T['siyan']);
$govde = $r($T['mor'], 40); // blok kenarlarindaki koyu ton
$vurgu = $r($T['amber']);
// Son 10 saniyede rakamlar pembeye doner - aciliyet hissi.
if ($kalan <= 10) {
$acik = $r($T['magenta']);
}
// ---------- RAKAMLARI YERLESTIR ----------
// Rakamlar soldan basliyor; Ralph icin sagda yer ayrilmisti,
// bu yuzden ikisi hicbir zaman cakismaz.
$x = $eSol + 10;
$ry = $RAKAM_UST;
foreach ($vals as $g => $deger) {
$n = $BASAMAKLAR[$g];
$gy = $ry;
for ($d = 0; $d < $n; $d++) {
$hx = $x + $d * ($HANE_G + $HANE_BOS);
rakamCiz($im, $deger[$d], $hx, $gy, $PIX, $acik, $govde);
}
// Etiket
$grupMerkez = $x + $grupGen[$g] / 2;
yaz($im, $ETIKETLER[$g], 7, $grupMerkez, $ETIKET_Y, $r($T['baslik']), $FONT, $ttf, 2);
$x += $grupGen[$g];
// Ayirac
if ($g < $ADET - 1) {
// Yanip sonme: vurus aninda parlak
$aRenk = ($adim === 0) ? $vurgu : $r($T['amber'], 90);
ayiracCiz($im, $x + (int) round(($GRUP_BOS - $PIX) / 2), $gy, $PIX, $aRenk, $govde);
$x += $GRUP_BOS;
}
}
// ---------- TUGLA SIRASI + RALPH ----------
$tUst = $eAlt - $TUGLA_YUK + 4;
tuglaCiz(
$im, $eSol + 2, $eSag - 2, $tUst, $TUGLA_YUK - 6, $kirik,
[$r($T['tugla']), $r($T['tuglaKen']), $r($T['amber'], 40)]
);
// Ralph rakamlarin sagindaki kendi seridinde, tuglalarin uzerinde durur
$ralphP = $RALPH_P;
$ralphX = $eSag - $RALPH_G - 6;
$ralphY = $tUst - 12 * $ralphP + 2;
ralphCiz(
$im, $ralphX, $ralphY, $ralphP, $vurusFaz,
[
$r($T['ten']),
$r($T['tulum']),
[$r($T['amber'])][0],
$r($T['mor']),
$r($T['tuglaKen']),
]
);
// ---------- CRT TARAMA CIZGILERI ----------
tarama($im, $eSol, $eUst, $eSag, $eAlt, $r($T['ekran'], 100));
return $im;
}
/** CRT tarama cizgileri - her 3 pikselde bir yatay koyu cizgi. */
function tarama($im, $sol, $ust, $sag, $alt, $renk)
{
for ($y = $ust; $y <= $alt; $y += 3) {
imageline($im, $sol + 1, $y, $sag - 1, $y, $renk);
}
}
// ==================================================================
// GIF BIRLESTIRICI — isin can alici noktasi
//
// PHP'nin GD eklentisi animasyonlu GIF uretemez; imagegif() her
// seferinde tek kareli bir GIF verir. Bu yuzden animasyonu elle
// kuruyoruz:
//
// 1) Her kareyi ayri ayri cizip GD'ye tek kareli GIF yaptiriyoruz.
// 2) O GIF'lerin icini acip sikistirilmis goruntu verisini ve
// renk paletini cekiyoruz (asagidaki gifCoz fonksiyonu).
// 3) GIF89a dosya yapisini kendimiz yazip kareleri arka arkaya
// diziyoruz (en alttaki "GIF'I KUR" bolumu).
//
// Goruntu verisini yeniden sikistirmiyoruz; GD'nin sikistirdigi
// hazir bloklari oldugu gibi tasiyoruz. Bu yuzden hizli calisir.
// ==================================================================
/**
* Tek kareli bir GIF'in icinden renk paletini ve sikistirilmis
* goruntu verisini cikarir. GIF dosyasi bloklardan olusur; basindan
* baslayip blok blok ilerliyor, isimize yarayan yere gelince duruyoruz.
*/
function gifCoz($bin)
{
$packed = ord($bin[10]);
$p = 13;
$gct = '';
$gctBit = 0;
if ($packed & 0x80) {
$gctBit = $packed & 0x07;
$len = 3 * (1 << ($gctBit + 1));
$gct = substr($bin, $p, $len);
$p += $len;
}
$son = strlen($bin);
$seffafIdx = -1;
while ($p < $son) {
$b = ord($bin[$p]);
if ($b === 0x21) { // uzanti blogu
// Graphic Control Extension ise seffaf renk indeksini al
if ($p + 1 < $son && ord($bin[$p + 1]) === 0xF9) {
$bayrak = ord($bin[$p + 3]);
if ($bayrak & 0x01) { $seffafIdx = ord($bin[$p + 6]); }
}
$p += 2;
while ($p < $son && ($sz = ord($bin[$p])) !== 0) { $p += 1 + $sz; }
$p++;
} elseif ($b === 0x2C) { // goruntu tanimlayici
$ipacked = ord($bin[$p + 9]);
$p += 10;
$pal = $gct;
$bit = $gctBit;
if ($ipacked & 0x80) {
$bit = $ipacked & 0x07;
$len = 3 * (1 << ($bit + 1));
$pal = substr($bin, $p, $len);
$p += $len;
}
$lzwMin = $bin[$p];
$p++;
$veri = '';
while ($p < $son && ($sz = ord($bin[$p])) !== 0) {
$veri .= substr($bin, $p, 1 + $sz);
$p += 1 + $sz;
}
$veri .= "\x00";
return [
'pal' => $pal,
'bit' => $bit,
'min' => $lzwMin,
'veri' => $veri,
'seffaf' => $seffafIdx,
];
} else {
break;
}
}
return null;
}
/**
* Cizilmis bir resmi GIF verisine cevirir.
*
* $renkSayisi renk sayisini kisitlar (32, 64 gibi). Az renk =
* kucuk dosya. Tasarimda zaten az renk kullandigimiz icin gozle
* gorulur bir kayip olmuyor.
*
* Sira onemli: once renkler kisitlaniyor, SONRA saydam renk
* isaretleniyor. Tersi yapilirsa saydamlik kayboluyor.
*/
function gifBin($im, $renkSayisi = 0)
{
global $tema;
if ($renkSayisi > 0) {
imagetruecolortopalette($im, false, $renkSayisi);
}
if ($tema === 'seffaf') {
// Isaret rengini palette buluyoruz. Renk kisitlama sirasinda
// ton biraz kaymis olabilecegi icin en yakin esi ariyoruz.
$idx = imagecolorclosest($im, SEFFAF_R, SEFFAF_G, SEFFAF_B);
if ($idx >= 0) {
imagecolortransparent($im, $idx);
}
}
imagesavealpha($im, true);
ob_start();
imagegif($im);
return ob_get_clean();
}
// ==================================================================
// KARELERI URET
//
// Dosya boyutunu kucuk tutan numara burada. GIF'te her kareyi tam
// boy gondermek zorunda degilsiniz; sadece degisen bolgeyi gonderip
// "geri kalani oldugu gibi birak" diyebilirsiniz.
//
// Sayacta bir saniyede degisen sey cok az: bir iki rakam, yanip
// sonen iki nokta ve Ralph. Ekranin geri kalani ayni kaliyor. Bu
// yuzden her karede yalnizca degisen kucuk dikdortgenleri yaziyoruz.
// Fark: 210 KB yerine 75 KB.
// ==================================================================
/**
* Iki kareyi karsilastirip degisen bolgeleri dikdortgen listesi
* olarak dondurur.
*
* Neden tek dikdortgen degil de liste? Cunku rakamlar solda, Ralph
* sagda duruyor. Ikisini tek bir dikdortgene sigdirsaydik aradaki
* kocaman degismemis alan da her karede bosuna gonderilirdi.
* Onun yerine degisen sutunlari gruplayip her gruba kendi
* dikdortgenini veriyoruz - GIF ayni karede birden fazla parcayi
* zaten destekliyor.
*
* @return array [[sol, ust, genislik, yukseklik], ...]
* Hicbir sey degismemisse bos dizi doner.
*/
function farkKutulari($a, $b, $w, $h)
{
// Once degisen sutunlari ve satirlari isaretle
$sutun = array_fill(0, $w, false);
$ust = $h; $alt = -1;
for ($y = 0; $y < $h; $y++) {
$satirDegisti = false;
for ($x = 0; $x < $w; $x++) {
if (imagecolorat($a, $x, $y) !== imagecolorat($b, $x, $y)) {
$sutun[$x] = true;
$satirDegisti = true;
}
}
if ($satirDegisti) {
if ($y < $ust) { $ust = $y; }
$alt = $y;
}
}
if ($alt < 0) { return []; }
// Bitisik degisen sutunlari gruplara ayir.
// Aralarinda $ESIK'ten az bos sutun varsa ayni gruba katilir - cok sayida
// minik blok, palet yuku yuzunden tek bloktan pahali olurdu.
$ESIK = 24;
$gruplar = [];
$bas = -1;
$bosluk = 0;
for ($x = 0; $x < $w; $x++) {
if ($sutun[$x]) {
if ($bas < 0) { $bas = $x; }
$son = $x;
$bosluk = 0;
} elseif ($bas >= 0) {
$bosluk++;
if ($bosluk > $ESIK) {
$gruplar[] = [$bas, $son];
$bas = -1;
}
}
}
if ($bas >= 0) { $gruplar[] = [$bas, $son]; }
// Her grup icin dikey sinirlari daralt
$kutular = [];
foreach ($gruplar as $g) {
list($gs, $gson) = $g;
$gUst = $h; $gAlt = -1;
for ($y = $ust; $y <= $alt; $y++) {
for ($x = $gs; $x <= $gson; $x++) {
if (imagecolorat($a, $x, $y) !== imagecolorat($b, $x, $y)) {
if ($y < $gUst) { $gUst = $y; }
$gAlt = $y;
break;
}
}
}
if ($gAlt < 0) { continue; }
$kutular[] = [$gs, $gUst, $gson - $gs + 1, $gAlt - $gUst + 1];
}
return $kutular;
}
// Uretilen parcalar burada birikiyor. Her eleman:
// [sol, ust, goruntuVerisi, kareninSonuMu]
$kareler = [];
$oncekiIm = null; // karsilastirma icin bir onceki kare
$oncekiBitti = null;
// Saniye saniye ilerleyip her an icin bir kare ciziyoruz.
for ($i = 0; $i < $KARE_SAYISI; $i++) {
$kalan = $hedefTs - ($simdi + $i);
$bitti = ($kalan <= 0);
// Sure dolduktan sonra ekran hic degismiyor; tek bir
// "OYUN BITTI" karesi yeter, gerisini uretmiyoruz.
if ($bitti && $oncekiBitti) { continue; }
$im = kareCiz($kalan, $i);
if ($oncekiIm === null) {
// Ilk kare tam boy gonderilir - uzerine eklenecek bir sey
// olmadigi icin zemin gorevi gorur.
//
// Kucuk bir tuzak: gifBin() resmin renklerini yerinde
// degistirdigi icin, karsilastirmada kullanacagimiz temiz
// kopyayi onceden almamiz gerekiyor. Yoksa bir sonraki
// kareyle karsilastirdigimizda her piksel farkli cikardi.
$kopya = imagecreatetruecolor($W, $H);
imagealphablending($kopya, false);
imagesavealpha($kopya, true);
imagecopy($kopya, $im, 0, 0, 0, 0, $W, $H);
$kareler[] = [0, 0, gifBin($im, 128), true];
imagedestroy($im);
$im = $kopya;
} else {
$kutular = farkKutulari($oncekiIm, $im, $W, $H);
if (!$kutular) {
// Hicbir sey degismedi - kareyi atla
imagedestroy($im);
continue;
}
$sonIdx = count($kutular) - 1;
foreach ($kutular as $ki => $kutu) {
list($sol, $ust, $gen, $yuk) = $kutu;
$kirp = imagecreatetruecolor($gen, $yuk);
imagealphablending($kirp, false);
imagesavealpha($kirp, true);
imagecopy($kirp, $im, 0, 0, $sol, $ust, $gen, $yuk);
// Yalnizca son blok gecikme tasir; oncekiler ayni anda cizilir.
$kareler[] = [$sol, $ust, gifBin($kirp, 32), $ki === $sonIdx];
imagedestroy($kirp);
}
}
if ($oncekiIm !== null) { imagedestroy($oncekiIm); }
$oncekiIm = $im;
$oncekiBitti = $bitti;
}
if ($oncekiIm !== null) { imagedestroy($oncekiIm); }
// ==================================================================
// GIF'I KUR
//
// Elimizde kare kare goruntu parcalari var; simdi bunlari GIF89a
// dosya yapisina gore arka arkaya diziyoruz. Sira sabittir:
// basik bilgi -> renk paleti -> dongu ayari -> kareler -> bitis
// ==================================================================
$ilk = gifCoz($kareler[0][2]);
// --- Dosya basligi: imza, olculer, renk paleti ---
$out = "GIF89a";
$out .= pack('v', $W) . pack('v', $H);
$out .= chr(0x80 | 0x70 | $ilk['bit']) . "\x00\x00";
$out .= $ilk['pal'];
// --- Dongu ayari ---
// Tarayicilara animasyonun kac kez oynayacagini soyleyen blok.
// ($DONGU 1 ise bir kez oynar, 0 ise sonsuz doner.)
$out .= "\x21\xFF\x0B" . "NETSCAPE2.0" . "\x03\x01" . pack('v', $DONGU) . "\x00";
// --- Kareler ---
foreach ($kareler as $idx => $k) {
list($sol, $ust, $bin, $gecikmeVer) = $k;
$f = gifCoz($bin);
if ($f === null) { continue; }
// Her parcanin onune bir kontrol blogu koyuyoruz. Burada iki
// sey soyluyoruz: bu parca ne kadar ekranda kalacak ve
// silinmeden yerinde birakilacak (silinseydi altindaki kare
// kaybolur, ekran yanip sonerdi).
//
// Bir kareyi birden fazla parcaya bolmustuk. O parcalarin
// yalnizca sonuncusuna bekleme suresi veriyoruz; oncekiler
// 0 sureyle ard arda cizildigi icin izleyici tek kare gorur.
$bayrak = (1 << 2);
$seffafIdx = 0;
if ($f['seffaf'] >= 0) {
$bayrak |= 0x01;
$seffafIdx = $f['seffaf'];
}
$out .= "\x21\xF9\x04" . chr($bayrak)
. pack('v', $gecikmeVer ? $KARE_SURESI : 0)
. chr($seffafIdx) . "\x00";
// Parcanin ekranda nereye oturacagini ve olculerini yaziyoruz.
// Tam boy olmadigi icin konum bilgisi onemli: tarayici bu
// parcayi onceki karenin uzerine, tam bu noktaya cizecek.
$gen = ord($bin[6]) | (ord($bin[7]) << 8);
$yuk = ord($bin[8]) | (ord($bin[9]) << 8);
$out .= "\x2C" . pack('v', $sol) . pack('v', $ust) . pack('v', $gen) . pack('v', $yuk);
// Parcanin kendi renk paleti ve sikistirilmis goruntu verisi.
$out .= chr(0x80 | $f['bit']) . $f['pal'];
$out .= $f['min'] . $f['veri'];
}
// --- Dosya sonu isareti ---
$out .= "\x3B";
// ==================================================================
// ONBELLEGE YAZ
// ==================================================================
if ($onbellekDosya !== null) {
// Once gecici bir dosyaya yazip sonra asil adina tasiyoruz.
// Boylece ayni anda gelen baska bir istek, yarim yazilmis
// dosyayi okuyup bozuk resim gostermez.
$gecici = $onbellekDosya . '.' . getmypid() . '.tmp';
if (@file_put_contents($gecici, $out) === strlen($out)) {
if (!@rename($gecici, $onbellekDosya)) { @unlink($gecici); }
} else {
// Yazamadiysak (izin yok, disk dolu) sorun degil; resim
// yine gonderilir, sadece onbellekten yararlanilmaz.
@unlink($gecici);
}
// Ayarlari her degistirdiginizde yeni bir dosya adi olusur ve
// eskisi oldugu yerde kalir. Klasor sismesin diye ara sira
// (yaklasik her 50 istekte bir) 1 saatten eski dosyalari
// siliyoruz. Her istekte yapmak gereksiz yere disk yorardi.
if (mt_rand(1, 50) === 1) {
foreach ((array) @glob($ONBELLEK_DIZ . '/*.gif') as $eski) {
if (time() - (int) @filemtime($eski) > 3600) { @unlink($eski); }
}
}
}
// ==================================================================
// CIKTI
// ==================================================================
gifGonder($out); 
<?php
/**
* ARCADE GERI SAYIM SAYACI — animasyonlu GIF
* ---------------------------------------------------------------
*
* NEDEN GIF?
* PNG durgun bir resimdir; sayfa yenilenmeden degismez. GIF ise
* animasyon tasiyabildigi icin, sunucu istek geldigi anda 60
* saniyelik bir animasyon uretir ve tarayici bunu kendi oynatir.
* Sonuc: sayac sayfa yenilenmeden tiklar. Forumlar <img> etiketine
* izin verdigi icin de konu icinde, imzada, her yerde calisir.
*
* KULLANIM
* HTML : <img src="https://siteniz.com/index.php">
* BBCode : [img]https://siteniz.com/index.php[/img]
*
* TEMALAR (adresin sonuna eklenir)
* ?tema=koyu koyu zemin (varsayilan)
* ?tema=acik acik zemin
* ?tema=seffaf zemin saydam; forum kendi arka planini gosterir,
* yani hem koyu hem acik temali forumda uyar
*
* KURULUM
* 1) Bu dosyayi sunucuya atin.
* 2) Yanina "onbellek" adinda bos bir klasor acip yazma izni
* verin (755). Acmazsaniz yine calisir, sadece her istekte
* bastan uretir - yani daha yavas olur.
* 3) Turkce karakter (GUN yerine GÜN) isterseniz ayni klasore
* "font.ttf" atin. Rakamlar fonta ihtiyac duymaz, onlari kod
* kendi cizer; font yalnizca alt etiketler icindir.
*
* GEREKSINIM
* PHP + GD eklentisi (imagegif). Baska kutuphane yok.
*
* NOT
* Animasyon 60 saniyeliktir; sonunda son karede durur. Sayfa
* yenilendiginde bastan uretilir. Daha uzun sursun isterseniz
* asagidaki $KARE_SAYISI degerini buyutun (dosya boyutu artar).
*/
// ==================================================================
// AYARLAR — duzenlemeniz gereken tek bolum burasi
// ==================================================================
date_default_timezone_set('Europe/Istanbul');
// Geri sayimin bitecegi an. Bicim: 'YIL-AY-GUN SAAT:DAKIKA:SANIYE'
$HEDEF = '2026-09-15 14:13:00';
// Sayacin ustundeki tabelada yazan metin
$BASLIK = 'UGILABS - GIF Versiyon';
// Sure doldugunda ekranda yazacak metin
$BITTI_METNI = 'OYUN BITTI';
// ------------------------------------------------------------------
// ANIMASYON
// ------------------------------------------------------------------
// Kac saniyelik animasyon uretilsin. 60 = 1 dakika.
// Buyutursen daha uzun sure tiklar ama dosya buyur:
// 60 kare ~75 KB (1 dakika)
// 300 kare ~250 KB (5 dakika)
$KARE_SAYISI = 60;
// Her karenin ekranda kalma suresi (1/100 saniye cinsinden).
// 100 = 1 saniye. Sayac saniye saydigi icin bunu degistirme.
$KARE_SURESI = 100;
// 1 = bir kez oynar, sonra son karede durur.
// 0 = sonsuz doner. Surekli hareket eder ama her turda basa
// saracagi icin yanlis zaman gostermeye baslar. Sayac icin
// 1 dogrusu; sonsuz dongu ancak susleme amacli isine yarar.
$DONGU = 1;
// false yaparsan saniye hanesi kaybolur, sadece gun/saat/dakika
// kalir. O durumda animasyona gerek kalmaz, tek kare uretilir.
$SANIYE_GOSTER = true;
// ------------------------------------------------------------------
// ONBELLEK — paylasimli hostingde sunucuyu ayakta tutar
//
// Her istekte animasyonu bastan cizmek sunucuyu yorar (~0.8 sn).
// Populer bir konuda 50 kisi ayni anda sayfayi acarsa bu 40 saniye
// islemci demektir ve paylasimli hosting bunu kaldirmaz.
//
// Bu yuzden uretilen GIF diske yazilir; asagidaki sure boyunca
// gelen istekler yeniden cizmeden hazir dosyayi alir. 50 kisi ayni
// anda girse bile tek uretim yapilir.
//
// Sayacin dogrulugunu bozmaz: en fazla bu sure kadar geriden gelir,
// yani 3 saniyede kimsenin fark edecegi bir sapma olusmaz.
// ------------------------------------------------------------------
// Uretilen dosya kac saniye saklansin. 0 yazarsan onbellek kapanir.
$ONBELLEK_SN = 3;
// Dosyalarin yazilacagi klasor. Yazma izni yoksa sistem yine
// calisir, sadece her istekte bastan uretir.
$ONBELLEK_DIZ = __DIR__ . '/onbellek';
// ------------------------------------------------------------------
// FONT
// Rakamlar fonta ihtiyac duymaz - onlari kod nokta nokta cizer.
// Buradaki font yalnizca alt etiketler (GUN, SAAT...) ve baslik
// icindir. Turkce karakter isterseniz klasore font.ttf atin;
// bulunamazsa asagidaki sistem fontlari sirayla denenir.
// ------------------------------------------------------------------
$FONT_ADAYLARI = [
__DIR__ . '/font.ttf',
'C:/Windows/Fonts/consolab.ttf',
'/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',
'/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',
];
// ------------------------------------------------------------------
// TEMA — arcade dolabinin ekrani
// ------------------------------------------------------------------
$tema = isset($_GET['tema']) ? strtolower($_GET['tema']) : 'koyu';
if (!in_array($tema, ['koyu', 'acik', 'seffaf'], true)) { $tema = 'koyu'; }
$T = [
// Ortak arcade paleti - zeminden bagimsiz, her iki temada da okunur.
'siyan' => [ 34, 224, 232],
'magenta' => [255, 61, 139],
'amber' => [255, 197, 61],
'mor' => [ 27, 16, 53],
'ten' => [217, 142, 95],
'tulum' => [ 46, 92, 138],
'tugla' => [166, 74, 58],
'tuglaKen' => [ 92, 38, 30],
];
// 'ekran' her temada koyu kalir - CRT camı zeminden bagimsizdir.
// 'baslik' : ekranin ICINDEKI etiketler (hep koyu zemin uzerinde)
// 'marquee' : ekranin USTUNDEKI tabela yazisi (zemine gore degisir)
if ($tema === 'acik') {
$T['zemin'] = [232, 230, 240];
$T['ekran'] = [ 38, 24, 70];
$T['cerceve'] = [ 62, 44, 104];
$T['baslik'] = [166, 152, 208];
$T['marquee'] = [ 44, 30, 84]; // acik zeminde koyu yazi
$T['golge'] = [206, 202, 220];
} else {
$T['zemin'] = [ 16, 10, 30];
$T['ekran'] = [ 30, 18, 58];
$T['cerceve'] = [ 62, 44, 104];
$T['baslik'] = [148, 132, 196];
$T['marquee'] = [172, 156, 216];
$T['golge'] = [ 10, 6, 20];
}
// ==================================================================
// ONBELLEKTEN SUNMA
//
// Bu bolum dosyanin en basinda duruyor cunku amaci cizim islerine
// hic girmeden isi bitirmek. Yeterince taze bir dosya varsa onu
// gonderip cikiyoruz; asagidaki kodun tamami hic calismiyor.
// ==================================================================
/** Hazir GIF verisini tarayiciya gonderir ve betigi sonlandirir. */
function gifGonder($veri)
{
header('Content-Type: image/gif');
header('Content-Length: ' . strlen($veri));
// Tarayiciya "bu resmi saklama" diyoruz. Saklarsa sayfa
// yenilendiginde eski animasyonu gosterir ve sayac yanlis
// zamanda kalir.
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Expires: Sat, 01 Jan 2000 00:00:00 GMT');
echo $veri;
exit;
}
$onbellekDosya = null;
if ($ONBELLEK_SN > 0) {
// Dosya adini ayarlardan uretiyoruz: ayni ayarlar ayni dosyayi
// kullanir, farkli tema kendi dosyasini alir. Boylece temalar
// birbirine karismaz.
$anahtar = md5(implode('|', [
$HEDEF, $BASLIK, $BITTI_METNI, $tema,
$KARE_SAYISI, $KARE_SURESI, $DONGU, $SANIYE_GOSTER,
]));
if (!is_dir($ONBELLEK_DIZ)) { @mkdir($ONBELLEK_DIZ, 0755, true); }
$onbellekDosya = $ONBELLEK_DIZ . '/' . $anahtar . '.gif';
// Dosya var ve yeterince taze mi? Oyleyse hic cizmeden gonder.
if (is_readable($onbellekDosya)) {
$yas = time() - (int) @filemtime($onbellekDosya);
if ($yas >= 0 && $yas < $ONBELLEK_SN) {
$hazir = @file_get_contents($onbellekDosya);
if ($hazir !== false && strlen($hazir) > 0) {
gifGonder($hazir);
}
}
}
// Buraya dustuyse dosya yok ya da eskimis; asagida yeniden
// uretilip en sonda diske yazilacak.
}
// ==================================================================
// HAZIRLIK
// ==================================================================
$FONT = null;
foreach ($FONT_ADAYLARI as $f) {
if (is_readable($f)) { $FONT = $f; break; }
}
$ttf = ($FONT !== null);
$hedefTs = strtotime($HEDEF);
$simdi = time();
if (!$SANIYE_GOSTER) { $KARE_SAYISI = 1; }
$ETIKETLER = ['GUN', 'SAAT', 'DAKIKA', 'SANIYE'];
$ADET = $SANIYE_GOSTER ? 4 : 3;
// ------------------------------------------------------------------
// OLCULER
// Ekran bir arcade dolabinin CRT'si: disinda cerceve, icinde rakamlar,
// en altta Ralph'in yumruk attigi tugla siras var.
// ------------------------------------------------------------------
$PADDING = 14; // dis cerceve kalinligi
// Rakamlar 5x7 glif izgarasi. $PIX bir glif pikselinin blok boyutu.
$PIX = 6;
$HANE_G = 5 * $PIX; // bir rakam hanesinin genisligi
$RAKAM_YUK = 7 * $PIX; // glif yuksekligi
$HANE_BOS = $PIX; // ayni grup icindeki haneler arasi
$GRUP_BOS = $PIX * 3; // gruplar arasi (iki nokta buraya gelir)
// Gun hanesi 3 basamak olabilir (999 gun), digerleri hep 2 basamak.
$gunBasamak = max(2, strlen((string) (int) floor(max(0, $hedefTs - $simdi) / 86400)));
$BASAMAKLAR = [$gunBasamak, 2, 2, 2];
$BASAMAKLAR = array_slice($BASAMAKLAR, 0, $ADET);
$grupGen = [];
foreach ($BASAMAKLAR as $n) {
$grupGen[] = $n * $HANE_G + ($n - 1) * $HANE_BOS;
}
// Rakam bloklarinin toplam genisligi
$RAKAM_G = array_sum($grupGen) + ($ADET - 1) * $GRUP_BOS;
// Ralph rakamlarin SAGINDA kendi seridinde durur - etiketleri kapatmaz.
$RALPH_P = 3; // Ralph'in piksel blok boyutu
$RALPH_G = 13 * $RALPH_P; // Ralph + yumruk icin ayrilan genislik
$RALPH_BOS = 14; // rakamlarla Ralph arasi bosluk
$EKRAN_G = $RAKAM_G + $RALPH_BOS + $RALPH_G + 16;
$EKRAN_SOL = $PADDING;
$EKRAN_UST = 42; // ustte baslik seridi
$EKRAN_YUK = 92; // rakamlar + etiketler
$TUGLA_YUK = 26; // Ralph'in kirdigi tugla sirasi
$W = $EKRAN_G + $PADDING * 2;
$H = $EKRAN_UST + $EKRAN_YUK + $TUGLA_YUK + $PADDING;
// Rakamlarin dikey konumu ($RAKAM_YUK yukarida glif izgarasindan geldi)
$RAKAM_UST = $EKRAN_UST + 16;
$ETIKET_Y = $RAKAM_UST + $RAKAM_YUK + 16;
// ------------------------------------------------------------------
// SEFFAF TEMA ICIN ISARET RENGI
//
// GIF'te saydamlik, "su renk saydam sayilsin" diye tek bir rengi
// isaretleyerek olur. Dogrudan saydam cizemeyiz, cunku GIF'e
// cevirirken kullanilan imagetruecolortopalette() saydamlik
// bilgisini korumaz.
//
// Bu yuzden zemini once bu renkle boyuyoruz - tasarimda hic
// gecmeyen, kimseye benzemeyen parlak bir ton. GIF'e yazarken de
// "bu renk saydamdir" diye isaretliyoruz. Sonuc: forumun kendi
// arka plani resmin altindan gorunuyor.
// ------------------------------------------------------------------
define('SEFFAF_R', 255);
define('SEFFAF_G', 0);
define('SEFFAF_B', 254);
// ==================================================================
// ARCADE PIKSEL FONT
// Rakamlar 5x7 izgaraya cizilmis gercek bitmap glifler. Her "piksel"
// ekranda $p x $p'lik bir blok olur - klasik arcade skor tabelasi gibi.
// TTF'e bagli degil, her sunucuda birebir ayni gorunur.
// ==================================================================
/** 5x7 rakam glifleri. Her satir 5 karakter: '#' dolu, '.' bos. */
function glif($ch)
{
static $font = [
'0' => ['.###.',
'#...#',
'#..##',
'#.#.#',
'##..#',
'#...#',
'.###.'],
'1' => ['..#..',
'.##..',
'..#..',
'..#..',
'..#..',
'..#..',
'.###.'],
'2' => ['.###.',
'#...#',
'....#',
'...#.',
'..#..',
'.#...',
'#####'],
'3' => ['#####',
'...#.',
'..#..',
'...#.',
'....#',
'#...#',
'.###.'],
'4' => ['...#.',
'..##.',
'.#.#.',
'#..#.',
'#####',
'...#.',
'...#.'],
'5' => ['#####',
'#....',
'####.',
'....#',
'....#',
'#...#',
'.###.'],
'6' => ['..##.',
'.#...',
'#....',
'####.',
'#...#',
'#...#',
'.###.'],
'7' => ['#####',
'....#',
'...#.',
'..#..',
'.#...',
'.#...',
'.#...'],
'8' => ['.###.',
'#...#',
'#...#',
'.###.',
'#...#',
'#...#',
'.###.'],
'9' => ['.###.',
'#...#',
'#...#',
'.####',
'....#',
'...#.',
'.##..'],
];
return isset($font[$ch]) ? $font[$ch] : null;
}
/**
* Tek bir rakami piksel bloklariyla cizer.
*
* $p : bir glif pikselinin ekrandaki blok boyutu
* $acik : dolu piksel rengi
* $govde : blogun ust kenarinda kullanilan koyu ton (hacim hissi)
*/
function rakamCiz($im, $rakam, $x, $y, $p, $acik, $govde)
{
$g = glif($rakam);
if ($g === null) { return; }
foreach ($g as $sy => $satir) {
for ($sx = 0; $sx < 5; $sx++) {
if ($satir[$sx] !== '#') { continue; }
$x1 = $x + $sx * $p;
$y1 = $y + $sy * $p;
$x2 = $x1 + $p - 1;
$y2 = $y1 + $p - 1;
imagefilledrectangle($im, $x1, $y1, $x2, $y2, $acik);
// Her blogun alt-sag kenarinda bir tik koyu ton: pikseller
// birbirine yapismaz, izgara okunur kalir.
imageline($im, $x1, $y2, $x2, $y2, $govde);
imageline($im, $x2, $y1, $x2, $y2, $govde);
}
}
}
/** Iki nokta ayirac - glif izgarasiyla ayni blok boyutunda. */
function ayiracCiz($im, $x, $y, $p, $renk, $govde)
{
foreach ([1, 4] as $sy) {
$y1 = $y + $sy * $p + (int) round($p / 2);
imagefilledrectangle($im, $x, $y1, $x + $p - 1, $y1 + $p - 1, $renk);
imageline($im, $x, $y1 + $p - 1, $x + $p - 1, $y1 + $p - 1, $govde);
}
}
// ==================================================================
// CIZIM YARDIMCILARI
// ==================================================================
/** Yatayda ortalanmis metin. Harf araligi (tracking) destekli. */
function yaz($im, $metin, $boyut, $merkezX, $baselineY, $renk, $font, $ttf, $aralik = 0)
{
if ($ttf && $aralik > 0) {
// Harf harf cizip aralik veriyoruz - arcade etiketleri genis olur.
$harfler = preg_split('//u', $metin, -1, PREG_SPLIT_NO_EMPTY);
$toplam = 0;
$genler = [];
foreach ($harfler as $h) {
$bb = imagettfbbox($boyut, 0, $font, $h);
$g = $bb[2] - $bb[0];
$genler[] = $g;
$toplam += $g + $aralik;
}
$toplam -= $aralik;
$x = (int) ($merkezX - $toplam / 2);
foreach ($harfler as $i => $h) {
imagettftext($im, $boyut, 0, $x, $baselineY, $renk, $font, $h);
$x += $genler[$i] + $aralik;
}
return;
}
if ($ttf) {
$bbox = imagettfbbox($boyut, 0, $font, $metin);
$x = (int) ($merkezX - ($bbox[2] - $bbox[0]) / 2);
imagettftext($im, $boyut, 0, $x, $baselineY, $renk, $font, $metin);
} else {
$f = $boyut >= 18 ? 5 : ($boyut >= 11 ? 3 : 2);
// Dahili fontta aralik icin harf harf cizim
if ($aralik > 0) {
$bg = imagefontwidth($f);
$toplam = strlen($metin) * ($bg + $aralik) - $aralik;
$x = (int) ($merkezX - $toplam / 2);
$y = (int) ($baselineY - imagefontheight($f));
for ($i = 0; $i < strlen($metin); $i++) {
imagestring($im, $f, $x, $y, $metin[$i], $renk);
$x += $bg + $aralik;
}
return;
}
$x = (int) ($merkezX - imagefontwidth($f) * strlen($metin) / 2);
$y = (int) ($baselineY - imagefontheight($f));
imagestring($im, $f, $x, $y, $metin, $renk);
}
}
/** Belirli bir zaman icin hane metinlerini dondurur. */
function degerler($kalan, $adet, $gunBasamak)
{
if ($kalan < 0) { $kalan = 0; }
$v = [
str_pad((string) (int) floor($kalan / 86400), $gunBasamak, '0', STR_PAD_LEFT),
str_pad((string) (int) floor(($kalan % 86400) / 3600), 2, '0', STR_PAD_LEFT),
str_pad((string) (int) floor(($kalan % 3600) / 60), 2, '0', STR_PAD_LEFT),
str_pad((string) (int) ($kalan % 60), 2, '0', STR_PAD_LEFT),
];
return array_slice($v, 0, $adet);
}
// ------------------------------------------------------------------
// RALPH
//
// Karakteri hazir bir resimden almiyoruz, blok blok ciziyoruz.
// Kocaman yumruklar, mavi tulum, dagink portakal sac.
//
// Asagidaki $b() yardimcisi isi kolaylastiriyor: koordinatlari
// piksel yerine kucuk kareler halinde veriyoruz, o da $p ile
// carpip ekrana ciziyor. Yani $b(3, 2, 5, 3) demek "soldan 3.,
// yukaridan 2. kareden basla, 5 kare genis 3 kare yuksek bir
// dikdortgen doldur" demek.
// ------------------------------------------------------------------
/**
* Ralph'i cizer.
*
* $vurus yumrugun nerede oldugunu belirler:
* 0 = kol yukarida (hazirlaniyor)
* 1 = yumruk asagida (tuglaya indi)
* Arasindaki degerler ara konumlari verir.
*/
function ralphCiz($im, $x, $y, $p, $vurus, $renkler)
{
list($ten, $tulum, $sac, $koyu, $ayakkabi) = $renkler;
$b = function ($gx, $gy, $gw, $gh, $renk) use ($im, $x, $y, $p) {
imagefilledrectangle(
$im,
$x + $gx * $p,
$y + $gy * $p,
$x + ($gx + $gw) * $p - 1,
$y + ($gy + $gh) * $p - 1,
$renk
);
};
// Vurusta govde one dogru cokuyor
$eg = (int) round($vurus);
// --- Sac (portakal, dagink) ---
$b(3, 0 + $eg, 5, 1, $sac);
$b(2, 1 + $eg, 7, 1, $sac);
$b(2, 0 + $eg, 1, 1, $sac); // dagink tutamlar
$b(8, 0 + $eg, 1, 1, $sac);
// --- Yuz ---
$b(3, 2 + $eg, 5, 3, $ten);
$b(3, 3 + $eg, 1, 1, $koyu); // sol goz
$b(6, 3 + $eg, 1, 1, $koyu); // sag goz
$b(4, 4 + $eg, 3, 1, $koyu); // agiz - kararli ifade
// --- Govde (tulum) ---
$b(3, 5 + $eg, 5, 4, $tulum);
$b(4, 5 + $eg, 1, 1, $koyu); // tulum askilari
$b(6, 5 + $eg, 1, 1, $koyu);
// --- Sol kol + dev yumruk (sabit, yanda asili) ---
$b(2, 6 + $eg, 1, 2, $ten);
$b(0, 8 + $eg, 3, 3, $ten); // Ralph'in meshur kocaman eli
$b(0, 8 + $eg, 3, 1, $koyu);
// --- Sag kol + dev yumruk: vurusa gore yukaridan tuglalara iner ---
// $vurus 0 -> yumruk basin uzerinde, 1 -> yumruk zeminde
$yumrukY = (int) round(-2 + $vurus * 11);
// Omuzdan yumruga uzanan kol
$omuzY = 6 + $eg;
$ust = min($omuzY, $yumrukY + $eg);
$alt = max($omuzY, $yumrukY + $eg + 2);
$b(8, $ust, 1, max(1, $alt - $ust), $ten);
// Yumruk (3x3 blok - govdeden buyuk, karakterin imzasi)
$b(8, $yumrukY + $eg, 3, 3, $ten);
$b(8, $yumrukY + $eg, 3, 1, $koyu); // parmak eklemleri golgesi
// --- Bacaklar ---
$b(3, 9 + $eg, 2, 2, $tulum);
$b(6, 9 + $eg, 2, 2, $tulum);
$b(2, 11 + $eg, 3, 1, $ayakkabi);
$b(6, 11 + $eg, 3, 1, $ayakkabi);
}
/**
* Tugla sirasi. $kirikIndex >= 0 ise o tugla parcalanmis cizilir.
*/
function tuglaCiz($im, $sol, $sag, $ust, $yuk, $kirikIndex, $renkler)
{
list($tugla, $ken, $toz) = $renkler;
$tg = 26; // tugla genisligi
$adet = (int) ceil(($sag - $sol) / $tg);
for ($i = 0; $i < $adet; $i++) {
$x1 = $sol + $i * $tg;
$x2 = min($x1 + $tg - 2, $sag);
if ($x1 >= $sag) { break; }
if ($i === $kirikIndex) {
// Parcalanmis tugla: ust yarisi ucmus, altta molozlar
imagefilledrectangle($im, $x1, $ust + (int) ($yuk * 0.55), $x2, $ust + $yuk, $ken);
// Ucusan parcalar
imagefilledrectangle($im, $x1 + 3, $ust - 5, $x1 + 7, $ust - 1, $toz);
imagefilledrectangle($im, $x1 + 13, $ust - 8, $x1 + 16, $ust - 5, $toz);
imagefilledrectangle($im, $x2 - 5, $ust - 3, $x2 - 2, $ust, $toz);
continue;
}
imagefilledrectangle($im, $x1, $ust, $x2, $ust + $yuk, $tugla);
imagerectangle($im, $x1, $ust, $x2, $ust + $yuk, $ken);
// Ust kenar isigi
imageline($im, $x1 + 1, $ust + 1, $x2 - 1, $ust + 1, $ken);
}
}
// ==================================================================
// KARE CIZIMI
// ==================================================================
/**
* Tek bir kareyi cizer.
*
* @param int $kalan Kalan saniye
* @param int $faz Animasyon fazi (0..N) - yumruk ve titresim icin
*/
function kareCiz($kalan, $faz)
{
global $W, $H, $ADET, $PADDING, $PIX, $HANE_G, $HANE_BOS, $GRUP_BOS, $BASAMAKLAR,
$EKRAN_SOL, $EKRAN_UST, $EKRAN_G, $EKRAN_YUK, $TUGLA_YUK,
$RAKAM_UST, $RAKAM_YUK, $ETIKET_Y, $grupGen,
$RALPH_P, $RALPH_G, $RALPH_BOS,
$BASLIK, $BITTI_METNI, $ETIKETLER, $FONT, $ttf, $T, $tema, $gunBasamak;
$im = imagecreatetruecolor($W, $H);
imagealphablending($im, false);
imagesavealpha($im, true);
$r = function ($c, $a = 0) use ($im) {
return imagecolorallocatealpha($im, $c[0], $c[1], $c[2], $a);
};
// ---------- ZEMIN ----------
// Seffaf temada zemini isaret rengiyle boyuyoruz; o renk daha
// sonra GIF'e yazilirken saydam olarak isaretleniyor.
// (Ayrintili aciklama icin yukaridaki SEFFAF_R tanimina bakin.)
if ($tema === 'seffaf') {
imagefilledrectangle(
$im, 0, 0, $W - 1, $H - 1,
imagecolorallocate($im, SEFFAF_R, SEFFAF_G, SEFFAF_B)
);
} else {
imagefilledrectangle($im, 0, 0, $W - 1, $H - 1, $r($T['zemin']));
}
imagealphablending($im, true);
// ---------- DOLAP CERCEVESI ----------
// Ekranin cevresinde ince cift cizgi - arcade dolabinin bezel'i.
// Seffaf temada yari saydam tonlar nobetci rengi karistiracagi icin
// tum cerceve ogeleri opak cizilir.
$sf = ($tema === 'seffaf');
$cerceve = $r($T['cerceve']);
imagerectangle($im, 1, 1, $W - 2, $H - 2, $cerceve);
imagerectangle($im, 3, 3, $W - 4, $H - 4, $sf ? $cerceve : $r($T['cerceve'], 60));
// Kose vidalari (dolap detayi)
$vida = $sf ? $r($T['baslik']) : $r($T['baslik'], 40);
foreach ([[7, 7], [$W - 8, 7], [7, $H - 8], [$W - 8, $H - 8]] as $v) {
imagefilledellipse($im, $v[0], $v[1], 3, 3, $vida);
}
// ---------- BASLIK SERIDI ----------
// Marquee: dolabin ustundeki isikli tabela.
// Seffaf temada opak cizilir - yoksa altindaki nobetci renk sizar.
$mSol = $PADDING;
$mSag = $W - $PADDING;
if ($tema === 'acik') {
// Acik temada tabela zeminle ayni tonda kalir, yazi koyu olur.
imagefilledrectangle($im, $mSol, 10, $mSag, 30, $r($T['golge']));
} else {
imagefilledrectangle(
$im, $mSol, 10, $mSag, 30,
($tema === 'seffaf') ? $r($T['ekran']) : $r($T['ekran'], 40)
);
}
imagerectangle($im, $mSol, 10, $mSag, 30, $r($T['cerceve'], 50));
yaz($im, $BASLIK, 8, $W / 2, 25, $r($T['marquee']), $FONT, $ttf, 2);
// ---------- CRT EKRANI ----------
$eSol = $EKRAN_SOL;
$eSag = $W - $PADDING;
$eUst = $EKRAN_UST;
$eAlt = $H - $PADDING;
imagefilledrectangle($im, $eSol, $eUst, $eSag, $eAlt, $r($T['ekran']));
// Ekran ici parlama: ustten asagi sonen ince cam yansimasi
for ($i = 0; $i < 10; $i++) {
imagefilledrectangle(
$im,
$eSol + 1, $eUst + 1 + $i,
$eSag - 1, $eUst + 1 + $i,
$r($T['siyan'], 116 + $i)
);
}
// ---------- SURE DOLDU ----------
// Ralph isini bitirmis: tuglalar yikilmis, molozun uzerinde duruyor.
if ($kalan <= 0) {
$tUstB = $eAlt - $TUGLA_YUK + 4;
// Yikilmis tugla sirasi - hepsi kirik, yalnizca moloz kalmis
$tg = 26;
for ($bx = $eSol + 2; $bx < $eSag - 2; $bx += $tg) {
$bx2 = min($bx + $tg - 2, $eSag - 2);
$yh = (int) (($TUGLA_YUK - 6) * 0.45);
imagefilledrectangle(
$im, $bx, $tUstB + ($TUGLA_YUK - 6) - $yh, $bx2, $tUstB + $TUGLA_YUK - 6,
$r($T['tuglaKen'])
);
}
yaz($im, $BITTI_METNI, 18, $W / 2, $eUst + 52, $r($T['magenta']), $FONT, $ttf, 4);
// Ralph, molozun uzerinde - yumruk asagida, is bitmis
ralphCiz(
$im, $eSag - $RALPH_G - 6, $tUstB - 12 * $RALPH_P + 2, $RALPH_P, 1.0,
[
$r($T['ten']), $r($T['tulum']), $r($T['amber']),
$r($T['mor']), $r($T['tuglaKen']),
]
);
tarama($im, $eSol, $eUst, $eSag, $eAlt, $r($T['ekran'], 100));
return $im;
}
$vals = degerler($kalan, $ADET, $gunBasamak);
// ---------- VURUS RITMI ----------
// Ralph her saniye bir kez vuruyor: kareler sirayla "yumruk
// asagida" ve "kol yukarida" diye gidiyor. Bu yuzden tek sayili
// ve cift sayili kareler farkli cizilir.
//
// Rakamlarin kendisi sabit duruyor. Vurusta onlari da
// titretmeyi denedim ama o zaman her karede butun rakam satiri
// degismis sayiliyor ve dosya boyutu ucuyor. Hareket sadece
// Ralph'ta ve tuglalarda kaliyor.
$kirik = -1;
$adim = $faz % 2; // 0 = yumruk zeminde, 1 = kol yukarida
if ($adim === 0) {
$vurusFaz = 1.0;
// Kirilan tuglayi her saniye degistiriyoruz ki Ralph hep
// ayni yere vurmasin.
$kirik = ((int) $kalan) % 9;
} else {
$vurusFaz = 0.15;
}
// ---------- RENKLER ----------
$acik = $r($T['siyan']);
$govde = $r($T['mor'], 40); // blok kenarlarindaki koyu ton
$vurgu = $r($T['amber']);
// Son 10 saniyede rakamlar pembeye doner - aciliyet hissi.
if ($kalan <= 10) {
$acik = $r($T['magenta']);
}
// ---------- RAKAMLARI YERLESTIR ----------
// Rakamlar soldan basliyor; Ralph icin sagda yer ayrilmisti,
// bu yuzden ikisi hicbir zaman cakismaz.
$x = $eSol + 10;
$ry = $RAKAM_UST;
foreach ($vals as $g => $deger) {
$n = $BASAMAKLAR[$g];
$gy = $ry;
for ($d = 0; $d < $n; $d++) {
$hx = $x + $d * ($HANE_G + $HANE_BOS);
rakamCiz($im, $deger[$d], $hx, $gy, $PIX, $acik, $govde);
}
// Etiket
$grupMerkez = $x + $grupGen[$g] / 2;
yaz($im, $ETIKETLER[$g], 7, $grupMerkez, $ETIKET_Y, $r($T['baslik']), $FONT, $ttf, 2);
$x += $grupGen[$g];
// Ayirac
if ($g < $ADET - 1) {
// Yanip sonme: vurus aninda parlak
$aRenk = ($adim === 0) ? $vurgu : $r($T['amber'], 90);
ayiracCiz($im, $x + (int) round(($GRUP_BOS - $PIX) / 2), $gy, $PIX, $aRenk, $govde);
$x += $GRUP_BOS;
}
}
// ---------- TUGLA SIRASI + RALPH ----------
$tUst = $eAlt - $TUGLA_YUK + 4;
tuglaCiz(
$im, $eSol + 2, $eSag - 2, $tUst, $TUGLA_YUK - 6, $kirik,
[$r($T['tugla']), $r($T['tuglaKen']), $r($T['amber'], 40)]
);
// Ralph rakamlarin sagindaki kendi seridinde, tuglalarin uzerinde durur
$ralphP = $RALPH_P;
$ralphX = $eSag - $RALPH_G - 6;
$ralphY = $tUst - 12 * $ralphP + 2;
ralphCiz(
$im, $ralphX, $ralphY, $ralphP, $vurusFaz,
[
$r($T['ten']),
$r($T['tulum']),
[$r($T['amber'])][0],
$r($T['mor']),
$r($T['tuglaKen']),
]
);
// ---------- CRT TARAMA CIZGILERI ----------
tarama($im, $eSol, $eUst, $eSag, $eAlt, $r($T['ekran'], 100));
return $im;
}
/** CRT tarama cizgileri - her 3 pikselde bir yatay koyu cizgi. */
function tarama($im, $sol, $ust, $sag, $alt, $renk)
{
for ($y = $ust; $y <= $alt; $y += 3) {
imageline($im, $sol + 1, $y, $sag - 1, $y, $renk);
}
}
// ==================================================================
// GIF BIRLESTIRICI — isin can alici noktasi
//
// PHP'nin GD eklentisi animasyonlu GIF uretemez; imagegif() her
// seferinde tek kareli bir GIF verir. Bu yuzden animasyonu elle
// kuruyoruz:
//
// 1) Her kareyi ayri ayri cizip GD'ye tek kareli GIF yaptiriyoruz.
// 2) O GIF'lerin icini acip sikistirilmis goruntu verisini ve
// renk paletini cekiyoruz (asagidaki gifCoz fonksiyonu).
// 3) GIF89a dosya yapisini kendimiz yazip kareleri arka arkaya
// diziyoruz (en alttaki "GIF'I KUR" bolumu).
//
// Goruntu verisini yeniden sikistirmiyoruz; GD'nin sikistirdigi
// hazir bloklari oldugu gibi tasiyoruz. Bu yuzden hizli calisir.
// ==================================================================
/**
* Tek kareli bir GIF'in icinden renk paletini ve sikistirilmis
* goruntu verisini cikarir. GIF dosyasi bloklardan olusur; basindan
* baslayip blok blok ilerliyor, isimize yarayan yere gelince duruyoruz.
*/
function gifCoz($bin)
{
$packed = ord($bin[10]);
$p = 13;
$gct = '';
$gctBit = 0;
if ($packed & 0x80) {
$gctBit = $packed & 0x07;
$len = 3 * (1 << ($gctBit + 1));
$gct = substr($bin, $p, $len);
$p += $len;
}
$son = strlen($bin);
$seffafIdx = -1;
while ($p < $son) {
$b = ord($bin[$p]);
if ($b === 0x21) { // uzanti blogu
// Graphic Control Extension ise seffaf renk indeksini al
if ($p + 1 < $son && ord($bin[$p + 1]) === 0xF9) {
$bayrak = ord($bin[$p + 3]);
if ($bayrak & 0x01) { $seffafIdx = ord($bin[$p + 6]); }
}
$p += 2;
while ($p < $son && ($sz = ord($bin[$p])) !== 0) { $p += 1 + $sz; }
$p++;
} elseif ($b === 0x2C) { // goruntu tanimlayici
$ipacked = ord($bin[$p + 9]);
$p += 10;
$pal = $gct;
$bit = $gctBit;
if ($ipacked & 0x80) {
$bit = $ipacked & 0x07;
$len = 3 * (1 << ($bit + 1));
$pal = substr($bin, $p, $len);
$p += $len;
}
$lzwMin = $bin[$p];
$p++;
$veri = '';
while ($p < $son && ($sz = ord($bin[$p])) !== 0) {
$veri .= substr($bin, $p, 1 + $sz);
$p += 1 + $sz;
}
$veri .= "\x00";
return [
'pal' => $pal,
'bit' => $bit,
'min' => $lzwMin,
'veri' => $veri,
'seffaf' => $seffafIdx,
];
} else {
break;
}
}
return null;
}
/**
* Cizilmis bir resmi GIF verisine cevirir.
*
* $renkSayisi renk sayisini kisitlar (32, 64 gibi). Az renk =
* kucuk dosya. Tasarimda zaten az renk kullandigimiz icin gozle
* gorulur bir kayip olmuyor.
*
* Sira onemli: once renkler kisitlaniyor, SONRA saydam renk
* isaretleniyor. Tersi yapilirsa saydamlik kayboluyor.
*/
function gifBin($im, $renkSayisi = 0)
{
global $tema;
if ($renkSayisi > 0) {
imagetruecolortopalette($im, false, $renkSayisi);
}
if ($tema === 'seffaf') {
// Isaret rengini palette buluyoruz. Renk kisitlama sirasinda
// ton biraz kaymis olabilecegi icin en yakin esi ariyoruz.
$idx = imagecolorclosest($im, SEFFAF_R, SEFFAF_G, SEFFAF_B);
if ($idx >= 0) {
imagecolortransparent($im, $idx);
}
}
imagesavealpha($im, true);
ob_start();
imagegif($im);
return ob_get_clean();
}
// ==================================================================
// KARELERI URET
//
// Dosya boyutunu kucuk tutan numara burada. GIF'te her kareyi tam
// boy gondermek zorunda degilsiniz; sadece degisen bolgeyi gonderip
// "geri kalani oldugu gibi birak" diyebilirsiniz.
//
// Sayacta bir saniyede degisen sey cok az: bir iki rakam, yanip
// sonen iki nokta ve Ralph. Ekranin geri kalani ayni kaliyor. Bu
// yuzden her karede yalnizca degisen kucuk dikdortgenleri yaziyoruz.
// Fark: 210 KB yerine 75 KB.
// ==================================================================
/**
* Iki kareyi karsilastirip degisen bolgeleri dikdortgen listesi
* olarak dondurur.
*
* Neden tek dikdortgen degil de liste? Cunku rakamlar solda, Ralph
* sagda duruyor. Ikisini tek bir dikdortgene sigdirsaydik aradaki
* kocaman degismemis alan da her karede bosuna gonderilirdi.
* Onun yerine degisen sutunlari gruplayip her gruba kendi
* dikdortgenini veriyoruz - GIF ayni karede birden fazla parcayi
* zaten destekliyor.
*
* @return array [[sol, ust, genislik, yukseklik], ...]
* Hicbir sey degismemisse bos dizi doner.
*/
function farkKutulari($a, $b, $w, $h)
{
// Once degisen sutunlari ve satirlari isaretle
$sutun = array_fill(0, $w, false);
$ust = $h; $alt = -1;
for ($y = 0; $y < $h; $y++) {
$satirDegisti = false;
for ($x = 0; $x < $w; $x++) {
if (imagecolorat($a, $x, $y) !== imagecolorat($b, $x, $y)) {
$sutun[$x] = true;
$satirDegisti = true;
}
}
if ($satirDegisti) {
if ($y < $ust) { $ust = $y; }
$alt = $y;
}
}
if ($alt < 0) { return []; }
// Bitisik degisen sutunlari gruplara ayir.
// Aralarinda $ESIK'ten az bos sutun varsa ayni gruba katilir - cok sayida
// minik blok, palet yuku yuzunden tek bloktan pahali olurdu.
$ESIK = 24;
$gruplar = [];
$bas = -1;
$bosluk = 0;
for ($x = 0; $x < $w; $x++) {
if ($sutun[$x]) {
if ($bas < 0) { $bas = $x; }
$son = $x;
$bosluk = 0;
} elseif ($bas >= 0) {
$bosluk++;
if ($bosluk > $ESIK) {
$gruplar[] = [$bas, $son];
$bas = -1;
}
}
}
if ($bas >= 0) { $gruplar[] = [$bas, $son]; }
// Her grup icin dikey sinirlari daralt
$kutular = [];
foreach ($gruplar as $g) {
list($gs, $gson) = $g;
$gUst = $h; $gAlt = -1;
for ($y = $ust; $y <= $alt; $y++) {
for ($x = $gs; $x <= $gson; $x++) {
if (imagecolorat($a, $x, $y) !== imagecolorat($b, $x, $y)) {
if ($y < $gUst) { $gUst = $y; }
$gAlt = $y;
break;
}
}
}
if ($gAlt < 0) { continue; }
$kutular[] = [$gs, $gUst, $gson - $gs + 1, $gAlt - $gUst + 1];
}
return $kutular;
}
// Uretilen parcalar burada birikiyor. Her eleman:
// [sol, ust, goruntuVerisi, kareninSonuMu]
$kareler = [];
$oncekiIm = null; // karsilastirma icin bir onceki kare
$oncekiBitti = null;
// Saniye saniye ilerleyip her an icin bir kare ciziyoruz.
for ($i = 0; $i < $KARE_SAYISI; $i++) {
$kalan = $hedefTs - ($simdi + $i);
$bitti = ($kalan <= 0);
// Sure dolduktan sonra ekran hic degismiyor; tek bir
// "OYUN BITTI" karesi yeter, gerisini uretmiyoruz.
if ($bitti && $oncekiBitti) { continue; }
$im = kareCiz($kalan, $i);
if ($oncekiIm === null) {
// Ilk kare tam boy gonderilir - uzerine eklenecek bir sey
// olmadigi icin zemin gorevi gorur.
//
// Kucuk bir tuzak: gifBin() resmin renklerini yerinde
// degistirdigi icin, karsilastirmada kullanacagimiz temiz
// kopyayi onceden almamiz gerekiyor. Yoksa bir sonraki
// kareyle karsilastirdigimizda her piksel farkli cikardi.
$kopya = imagecreatetruecolor($W, $H);
imagealphablending($kopya, false);
imagesavealpha($kopya, true);
imagecopy($kopya, $im, 0, 0, 0, 0, $W, $H);
$kareler[] = [0, 0, gifBin($im, 128), true];
imagedestroy($im);
$im = $kopya;
} else {
$kutular = farkKutulari($oncekiIm, $im, $W, $H);
if (!$kutular) {
// Hicbir sey degismedi - kareyi atla
imagedestroy($im);
continue;
}
$sonIdx = count($kutular) - 1;
foreach ($kutular as $ki => $kutu) {
list($sol, $ust, $gen, $yuk) = $kutu;
$kirp = imagecreatetruecolor($gen, $yuk);
imagealphablending($kirp, false);
imagesavealpha($kirp, true);
imagecopy($kirp, $im, 0, 0, $sol, $ust, $gen, $yuk);
// Yalnizca son blok gecikme tasir; oncekiler ayni anda cizilir.
$kareler[] = [$sol, $ust, gifBin($kirp, 32), $ki === $sonIdx];
imagedestroy($kirp);
}
}
if ($oncekiIm !== null) { imagedestroy($oncekiIm); }
$oncekiIm = $im;
$oncekiBitti = $bitti;
}
if ($oncekiIm !== null) { imagedestroy($oncekiIm); }
// ==================================================================
// GIF'I KUR
//
// Elimizde kare kare goruntu parcalari var; simdi bunlari GIF89a
// dosya yapisina gore arka arkaya diziyoruz. Sira sabittir:
// basik bilgi -> renk paleti -> dongu ayari -> kareler -> bitis
// ==================================================================
$ilk = gifCoz($kareler[0][2]);
// --- Dosya basligi: imza, olculer, renk paleti ---
$out = "GIF89a";
$out .= pack('v', $W) . pack('v', $H);
$out .= chr(0x80 | 0x70 | $ilk['bit']) . "\x00\x00";
$out .= $ilk['pal'];
// --- Dongu ayari ---
// Tarayicilara animasyonun kac kez oynayacagini soyleyen blok.
// ($DONGU 1 ise bir kez oynar, 0 ise sonsuz doner.)
$out .= "\x21\xFF\x0B" . "NETSCAPE2.0" . "\x03\x01" . pack('v', $DONGU) . "\x00";
// --- Kareler ---
foreach ($kareler as $idx => $k) {
list($sol, $ust, $bin, $gecikmeVer) = $k;
$f = gifCoz($bin);
if ($f === null) { continue; }
// Her parcanin onune bir kontrol blogu koyuyoruz. Burada iki
// sey soyluyoruz: bu parca ne kadar ekranda kalacak ve
// silinmeden yerinde birakilacak (silinseydi altindaki kare
// kaybolur, ekran yanip sonerdi).
//
// Bir kareyi birden fazla parcaya bolmustuk. O parcalarin
// yalnizca sonuncusuna bekleme suresi veriyoruz; oncekiler
// 0 sureyle ard arda cizildigi icin izleyici tek kare gorur.
$bayrak = (1 << 2);
$seffafIdx = 0;
if ($f['seffaf'] >= 0) {
$bayrak |= 0x01;
$seffafIdx = $f['seffaf'];
}
$out .= "\x21\xF9\x04" . chr($bayrak)
. pack('v', $gecikmeVer ? $KARE_SURESI : 0)
. chr($seffafIdx) . "\x00";
// Parcanin ekranda nereye oturacagini ve olculerini yaziyoruz.
// Tam boy olmadigi icin konum bilgisi onemli: tarayici bu
// parcayi onceki karenin uzerine, tam bu noktaya cizecek.
$gen = ord($bin[6]) | (ord($bin[7]) << 8);
$yuk = ord($bin[8]) | (ord($bin[9]) << 8);
$out .= "\x2C" . pack('v', $sol) . pack('v', $ust) . pack('v', $gen) . pack('v', $yuk);
// Parcanin kendi renk paleti ve sikistirilmis goruntu verisi.
$out .= chr(0x80 | $f['bit']) . $f['pal'];
$out .= $f['min'] . $f['veri'];
}
// --- Dosya sonu isareti ---
$out .= "\x3B";
// ==================================================================
// ONBELLEGE YAZ
// ==================================================================
if ($onbellekDosya !== null) {
// Once gecici bir dosyaya yazip sonra asil adina tasiyoruz.
// Boylece ayni anda gelen baska bir istek, yarim yazilmis
// dosyayi okuyup bozuk resim gostermez.
$gecici = $onbellekDosya . '.' . getmypid() . '.tmp';
if (@file_put_contents($gecici, $out) === strlen($out)) {
if (!@rename($gecici, $onbellekDosya)) { @unlink($gecici); }
} else {
// Yazamadiysak (izin yok, disk dolu) sorun degil; resim
// yine gonderilir, sadece onbellekten yararlanilmaz.
@unlink($gecici);
}
// Ayarlari her degistirdiginizde yeni bir dosya adi olusur ve
// eskisi oldugu yerde kalir. Klasor sismesin diye ara sira
// (yaklasik her 50 istekte bir) 1 saatten eski dosyalari
// siliyoruz. Her istekte yapmak gereksiz yere disk yorardi.
if (mt_rand(1, 50) === 1) {
foreach ((array) @glob($ONBELLEK_DIZ . '/*.gif') as $eski) {
if (time() - (int) @filemtime($eski) > 3600) { @unlink($eski); }
}
}
}
// ==================================================================
// CIKTI
// ==================================================================
gifGonder($out); 
