0.9.0 - the ZapV version updated to current php standards. Brave search used, while Bing and Google search api are deprecated

This commit is contained in:
Fabian de Boer 2026-07-15 13:57:05 +02:00
commit 4ddaddda3d
31 changed files with 1778 additions and 1 deletions

50
lib/ImgIOTools.class.php Executable file
View file

@ -0,0 +1,50 @@
<?php
/**
* Image Input Output Tools
*/
class ImgIOTools {
public static $maxScreenWidth = 1067; //screen dimensions
public static $maxScreenHeight = 600;
/**
* Flushes image to screen
* @param Imagick $im
* @return void
*/
public static function onScreen(Imagick $img) {
$img->scaleImage(self::$maxScreenWidth, self::$maxScreenHeight, true);
//$img = self::scaleByLength($img, $this->maxScreenWidth, $this->maxScreenHeight);
header('Content-type: image/' . ZAP_IMG_TYPE);
echo $img->getImageBlob();
}
/**
* Puts image from $imgUrl (if exists) onscreen
* @param string $imgUrl
* @return boolean or imagedata
*/
public static function urlOnScreen($imgUrl) {
if (file_exists($imgUrl)) {
$img = new Imagick($imgUrl);
self::onScreen($img);
} else {
return false;
}
}
/**
* Saves active image from $im as $filename.$filetype in $folder
* @param Imagick $im
* @param string $folder
* @param string $filename
* @param string $filetype
* @return boolean
*/
public static function saveImg(Imagick $img, $folder, $filename) {
$path = $folder . $filename . '.' . ZAP_IMG_TYPE;
return $img->writeImage($path);
}
}
?>

92
lib/ImgProcess.class.php Executable file
View file

@ -0,0 +1,92 @@
<?php
/**
* ImgProcess
* Actual image manipulation
*/
class ImgProcess {
private $folder; //folder for reading source images and write
private $canvasName;
private $zapWidth;//collage dimensions
private $zapHeight;
private $maxRGB; //depends on colorDepth. 8 bits: 255; 16 bits : 65536;
/**
* constructor
* @param $imgFolder string
* @param $width int
* @param $height int
* @param $nr int
*/
public function __construct($imgFolder, $width, $height, $nr) {
$this->folder = $imgFolder;
$this->zapWidth = $width;
$this->zapHeight = $height;
$this->canvasName = DIRECTORY_SEPARATOR . ZAP_CANVAS_NAME;
$this->canvasName .= $_SESSION['date'] . "_nr_" . sprintf("%02d", $nr);
$tmpImg = new Imagick();
$tmpAr = $tmpImg->getQuantumRange();
$this->maxRGB = $tmpAr['quantumRangeLong'];
$tmpImg->destroy();
}
/**
* Makes a basic canvasFile from sourceImage
* and saves it into imageFolder
* returns imageBLOB from saved image
* @param string $sourceImgName
* @return void
*/
public function createBasicCanvas($sourceImgName) {
$canvas = new Imagick($sourceImgName);
$canvas = ImgTools::zoomCrop($canvas, $this->zapWidth, $this->zapHeight, TRUE);
ImgIOTools::saveImg($canvas, $this->folder, $this->canvasName);
if (ZAP_DELETE_SOURCE == true) {
unlink($sourceImgName);
}
ImgIOTools::onScreen($canvas);
}
/**
* adds processed version of $sourceImage to canvas
* @param string $sourceImgName
* @return void
*/
public function addToCanvas($sourceImgName) {
$canvasUri = $this->folder . $this->canvasName . '.' . ZAP_IMG_TYPE;
$canvas = new Imagick($canvasUri);
$source_im = new Imagick($sourceImgName);
$newCanvas_im = $this->zapToCanvas($source_im, $canvas);
ImgIOTools::saveImg($newCanvas_im, $this->folder, $this->canvasName);
if (ZAP_DELETE_SOURCE == true) {
unlink($sourceImgName);
}
ImgIOTools::onScreen($newCanvas_im);
}
/**
* @param Imagick $source_im
* @param Imagick $canvas
* @return Imagick Object
*/
private function zapToCanvas(Imagick $source_im, $canvas) {
//$val = ImgTools::imgCenterValueProm($source_im, $this->maxRGB);
//merge resized random transparised image on top of canvas
$transparent = ImgTools::transparantRandom($source_im, $this->maxRGB);
$resized = ImgTools::resize($transparent, $this->zapWidth, $this->zapHeight);
$canvas->compositeImage($resized, imagick::COMPOSITE_OVER, 0, 0);
return $canvas;
}
}
?>

145
lib/ImgSearch.class.php Executable file
View file

@ -0,0 +1,145 @@
<?php
/**
* ZAP image search class
*/
class ImgSearch {
private $hitPosition;
private $numberResults = 16;
private $folder;
private $word;
private $engine;
private $allowedFileTypes = array('image/jpeg','image/gif','image/png');
private $log;
/**
* Constructor
* @param string $imgFolder path to save images to
* @param string $word keyword to search for
*/
public function __construct($imgFolder, $word) {
$this->folder = $imgFolder;
$this->word = $word;
$this->hitPosition = (int) rand(0,10);
if (ZAP_LOG == true) {
$this->log = new Log($this->folder);
}
$config = Config::getInstance();
$engines = $config->retrieve('engines');
$this->engine = $engines[array_rand($engines)];
}
/**
* download image that corresponds to $keyWord
* returns filename of image if downloaded succeeded, or false if failed
* @return string $localUrl / boolean
*/
public function getImage() {
$imgData = $this->getImageData();
if ($imgData == false) {
$this->addLog('error','Search: ', 'bad search data received' );
return false;
}
$this->addLog('ok', 'Engine: ', $imgData['engine']);
$localUrl = $this->imageToTmp($imgData['results'], $this->folder);
return $localUrl;
}
/**
* Returns an array that contains searchdata for $word
* @return array $searchData or false
*/
public function getImageData() {
$clazz = ucfirst($this->engine);
$search = new $clazz();
$search->setQuery(urlencode($this->word));
$search->setParam('start', $this->hitPosition);
$search->setParam('size', $this->numberResults);
$searchData = $search->getData();
return $searchData['result'] == false ? false : $searchData;
}
/**
* Saves image or returns false
* @param array $results
* @param string $tmpDir
* @param integer $hit
* @return mixed
*/
private function imageToTmp($results, $tmpDir, $hit = 0) {
$first = $results[$hit]['clickurl'];
$imageName = $tmpDir . DIRECTORY_SEPARATOR . $this->filename($first);
$myCurl = new ZAPCurl($first, true);
$imageData = $myCurl->fetchCurlData();
if($imageData == false) {
$this->addLog('error', 'Search: ', 'received empty file' . PHP_EOL);
return false;
}
if(file_put_contents($imageName, $imageData)) {
if($this->imageTypeOk($imageName)) {
$this->addLog('ok', 'Search: ', "downloaded to $imageName");
$this->addLog('ok', 'Search: ', 'source: ' . $results[$hit]['referer']);
$this->addLog('ok', 'Search: ', 'thumb: ' . $results[$hit]['thumb'] . PHP_EOL);
return $imageName;
} else {
$this->addLog('error', 'Search: ', 'wrong file type: image removed');
unlink($imageName);
$nextHit = $hit + 1;
return $this->imageToTmp($results, $tmpDir, $nextHit);
}
} else {
$this->addLog('error', 'Search: ', 'failed saving image');
return false;
}
}
/**
* Check mime type of image
* @param string $fileUri
* @return boolean
*/
private function imageTypeOk($fileUri) {
$mimeType = mime_content_type($fileUri);
return in_array($mimeType, $this->allowedFileTypes);
}
/**
* Gets filename from a url
* @param string $urlString
* @return string
*/
private function filename($urlString) {
$ar = explode('/', $urlString);
$filename = array_pop($ar);
// Strip query string so the saved file has a real extension
$filename = explode('?', $filename)[0];
return $filename;
}
/**
* Log messages
*/
private function addLog($type, $head, $message) {
if (ZAP_LOG == true) {
$this->log->addToLog($type, $head, $message);
}
return;
}
}
?>

180
lib/ImgTools.class.php Executable file
View file

@ -0,0 +1,180 @@
<?php
/**
* Library of tool functions to manipulate an image
*/
class ImgTools {
public static $maxScreenWidth = 1067; //screen dimensions
public static $maxScreenHeight = 600;
public static $minFuzzFactor = 0.08; //minimal factor for $fuzz
public static $maxFuzzFactor = 0.64; //maximal factor for $fuzz
/**
* Returns $im with random transparent color
* @param Imagick $im
* @param int $maxRGB
* @return Imagick $im
*/
public static function transparantRandom(Imagick $img, $maxRGB) {
//compute random fuzzyness
$minFuzz = intval(self::$minFuzzFactor * $maxRGB);
$maxFuzz = intval(self::$maxFuzzFactor * $maxRGB);
$randFuzz = mt_rand ($minFuzz,$maxFuzz);
//take average RGB value of random pixel
$xPoint = mt_rand(0,$img->getImageWidth());
$yPoint = mt_rand(0,$img->getImageHeight());
$randomPixel = $img->getImagePixelColor($xPoint, $yPoint);
$randomPixelColor = $randomPixel->getColorAsString();
//make transparency
$img->transparentPaintImage($randomPixelColor, 0.0, $randFuzz, false);
return $img;
}
/**
* random Rotate image
* @param Imagick $im
* @return Imagick $im
*/
public static function randomRotate(Imagick $img) {
$transp = new ImagickPixel('#ffffff');
$ar = array(0,90,180,270);
$img->rotateImage($transp, $ar[mt_rand(0,3)]);
return $img;
}
/**
* Resize image
* @param Imagick $im
* @param integer $retWidth
* @param integer $retHeight
* @return Imagick $im
*/
public static function resize(Imagick $img, $retWidth, $retHeight) {
$img->resizeImage((int)$retWidth, (int)$retHeight, imagick::FILTER_POINT, 0);
return $img;
}
/**
* Recolor image
* @param Imagick $im
* @return Imagick $im
*/
public static function recolor(Imagick $img){
$recolorValue = mt_rand(4, 250);
$times = mt_rand(1, 4);
for($i = 0; $i <= $times; $i++) {
$img->cycleColormapImage($recolorValue);
}
return $img;
}
/**
* returns MagickWand with randomly zoomed part of image $im with size $retWidth * $retHeight
* @param Imagick $im
* @param integer $retWidth
* @param integer $retHeight
* @param boolean $rotate
*/
public static function zoomCrop(Imagick $img, $retWidth, $retHeight, $rotate = false) {
if ($rotate == true) {
$img = self::randomRotate($img);
}
$fact = mt_rand(10, 40) / 100; //zoomfactor 10 - 40%
$width = $img->getImageWidth();
$height = $img->getImageHeight();
$newWidth = (int)($width * $fact);
$newHeight = (int)($height * $fact);
//crop $im
//x and y offset random 0 - width (or height) of blown up image minus crop area (of course!)
$img->cropImage($newWidth, $newHeight,
mt_rand(0, (int)($width - $newWidth)),
mt_rand(0, (int)($height - $newHeight)));
$img->resizeImage((int)$retWidth, (int)$retHeight, imagick::FILTER_POINT, 0);
return $img;
}
/**
* converts all gif images in array to png images
* returns Array with converted filenames
* @param array $imgAr
* @return array $newArray
*/
public static function convertGifsToPng($imgAr) {
$newArray = array();
while ($filename = array_shift($imgAr)) {
$imgInfo = getimagesize($filename);
$mimeType = $imgInfo['mime'];
//find gif
if ($mimeType == "image/gif") {
$imagetype = 'png';
//full image name
$Dot = '.';
$newFilename = "$filename$Dot$imagetype";
//convert
$img = new Imagick($filename);
if($img->writeImage($newFilename)) {
//Remove original and rename new to save space
unlink($filename);
array_push($newArray, $newFilename);
}
} else {
array_push($newArray, $filename);
}
}
return $newArray;
}
/**
* LEGACY?
* INT (0 - 1000) -> promilage of average RGB value of center pixel of $im related to $maxRGB
* @param Imagick $im
* @param int $maxRGB
* @return int
*/
public static function imgCenterValueProm(Imagick $img, $maxRGB) {
$centerX = $img->getImageWidth() / 2;
$centerY = $img->getImageHeight() / 2;
//take average of the RGB of most middle pixel of each image and put it into an array
$centerPixel = $img->getImagePixelColor($centerX,$centerY);
$colors = $centerPixel->getColor();
$averageColor = round( ($colors['r'] + $colors['g'] + $colors['b']) / 3);
return round(($averageColor / $maxRGB) * 1000);
}
/**
* LEGACY?
* returns magickWand scaled proportionaly to $maxW or $maxH
* @param Imagick $im
* @param integer $maxW
* @param integer $maxH
* @return Imagick $im
*/
public static function scaleByLength(Imagick $img, $maxW, $maxH) {
$width = $img->getImageWidth();
$height = $img->getImageHeight();
if(($width <= self::$maxScreenWidth) && ($height <= self::$maxScreenHeight)) {
return $img;
} else {
if ($width >= $height) { //horizontal image
$new_x = $maxW;
$new_y = round(($new_x / $width) * $height, 0);
} else { //vertical image
$new_y = $maxH;
$new_x = round(($new_y / $height) * $width, 0);
}
return self::resize($img, $new_x, $new_y);
}
}
}
?>

41
lib/ZAPController.class.php Executable file
View file

@ -0,0 +1,41 @@
<?php
/**
* Frontend handler
*/
class ZAPController {
/**
* String mode
*/
private $mode;
/**
* Parsable content object
*/
private $action;
/**
* Class prefix
*/
private $prefix = 'ZAP';
/**
* Constructor
*/
public function __construct($mode = 'home') {
$mode = trim(strip_tags($mode));
$this->mode = ucfirst($mode);
$clazz = $this->prefix . $this->mode;
if (!class_exists($clazz)) {
die('Wrong parameter');
}
$this->action = new $clazz();
}
/**
* @return string content
*/
public function fetch() {
return $this->action->getContent();
}
}
?>

91
lib/ZAPDisplay.class.php Executable file
View file

@ -0,0 +1,91 @@
<?php
/**
* Class is responsible for starting up ZAP machine
*/
class ZAPDisplay {
/**
* Template file
*/
private $template = 'display';
/**
* Folder string
*/
private $folder = '';
/**
* Words array
*/
private $words = array();
/**
* Content string
*/
private $tpl = '';
/**
* Constructor
*/
public function __construct() {
ob_start();
$this->folder = $this->createFolder();
$this->words = $this->handleWords();
$_SESSION['words'] = $this->words;
$_SESSION['folder'] = $this->folder;
$_SESSION['collages'] = (int) $_POST['collages']; //number of zaps
$_SESSION['width'] = (int) $_POST['width'];
$_SESSION['height'] = (int) $_POST['height'];
$_SESSION['date'] = ZAP_MOMENT;
$ver = ZAP_VERSION;
include getZAPTemplate($this->template);
$this->tpl .= ob_get_clean();
}
/**
* Create folder and return path
* @return string $folder path or throws exception
*/
private function createFolder() {
try {
$folder = ZAP_APP_BASE_DIR . DIRECTORY_SEPARATOR
. ZAP_SESSIONS_DIR . DIRECTORY_SEPARATOR . 'zap_' . ZAP_MOMENT;
if (!@mkdir($folder, 0777)) {
throw new Exception('<p>' . ERROR_PREFIX . 'Unable to create base dir: ' . $folder . '</p>');
}
return $folder;
} catch (Exception $e) {
$this->tpl .= $e->getMessage();
return;
}
}
/**
* Create an array based on post data
* @return array $words
*/
private function handleWords() {
$words = array();
$ix = 1;
$postix = sprintf("%02d", $ix);
$wd = 'word' . $postix;
while(!empty($_POST[$wd])) {
array_push($words, $_POST[$wd]);
$ix++;
$postfix = sprintf("%02d", $ix);
$wd = 'word' . $postfix;
}
return $words;
}
/**
* return rendered content
*/
public function getContent() {
return $this->tpl;
}
}
?>

40
lib/ZAPHome.class.php Executable file
View file

@ -0,0 +1,40 @@
<?php
/**
* Index class
*/
class ZAPHome {
/**
* Template file prefix
*/
private $template = 'index';
/**
* ZAP version
*/
private $ver;
/**
* Parsable content
*/
private $tpl = '';
/**
* Constructor
*/
public function __construct() {
$this->ver = ZAP_VERSION;
extract(get_object_vars($this));
ob_start();
include getZAPTemplate($this->template);
$this->tpl .= ob_get_clean();
}
/**
* @return cotent string
*/
public function getContent() {
return $this->tpl;
}
}
?>

121
lib/ZAPZap.class.php Executable file
View file

@ -0,0 +1,121 @@
<?php
/**
* ZAP factory
*/
class ZAPZap {
private $tpl = '';
private $folder;
private $height;
private $width;
private $words;
public function __construct() {
ob_start();
if (!isset($_SESSION['collages']) || $_SESSION['collages'] <= 0) {
$this->destroySession();
$this->tpl .= ob_get_clean();
return;
}
$this->width = $_SESSION['width'];
$this->height = $_SESSION['height'];
$this->folder = $_SESSION['folder'];
$this->words = $_SESSION['words'];
if (!empty($this->words)) {
// Still downloading source images for the words.
$this->showFoundImage();
} elseif (empty($_SESSION['images'])) {
// Start a new collage.
$this->buildNextCollage();
} else {
// Add another source image to the current collage.
$imgName = array_shift($_SESSION['images']);
$image = $this->createImage($_SESSION['collageNr']);
$image->addToCanvas($imgName);
}
$this->tpl .= ob_get_clean();
}
/**
* Build the next collage if any remain, otherwise finish.
*/
private function buildNextCollage() {
if ($_SESSION['collages'] <= 0) {
$this->destroySession();
return;
}
// On the very first collage, load the downloaded source images.
// On later collages, reuse the original list.
if (!isset($_SESSION['imgOriginal'])) {
$_SESSION['images'] = ImgTools::convertGifsToPng($this->retrieveImages());
$_SESSION['imgOriginal'] = $_SESSION['images'];
} else {
$_SESSION['images'] = $_SESSION['imgOriginal'];
shuffle($_SESSION['images']);
}
if (empty($_SESSION['images'])) {
$this->destroySession();
return;
}
// Track the current collage number separately so addToCanvas always
// reads/writes the same file while this collage is being built.
$_SESSION['collageNr'] = $_SESSION['collages'];
$_SESSION['collages']--;
$firstIm = array_shift($_SESSION['images']);
$image = $this->createImage($_SESSION['collageNr']);
$image->createBasicCanvas($firstIm);
}
private function createImage($collages) {
return new ImgProcess($this->folder, $this->width, $this->height, $collages);
}
private function destroySession() {
if (Session::destroy()) {
$this->tpl .= "<p>" . ERROR_PREFIX . "Image ready</p>";
} else {
$this->tpl .= "<p>" . ERROR_PREFIX . "Strange... Could not end PHP session";
}
return;
}
private function setSessionVar($key, $value) {
$_SESSION[$key] = $value;
}
private function showFoundImage() {
$keyWord = array_shift($this->words);
$search = new ImgSearch($this->folder, $keyWord);
$imageUrl = $search->getImage();
if ($imageUrl) {
ImgIOTools::urlOnScreen($imageUrl);
} else {
ImgIOTools::urlOnScreen(ZAP_APP_BASE_DIR . DIRECTORY_SEPARATOR
. ZAP_WEB_DIR . DIRECTORY_SEPARATOR
. 'images' . DIRECTORY_SEPARATOR . 'error_noHit.png');
}
$this->setSessionVar('words', $this->words);
}
private function retrieveImages() {
$img_ar = glob('{' . $this->folder . DIRECTORY_SEPARATOR .'*.jpg,'
. $this->folder . DIRECTORY_SEPARATOR .'*.png,'
. $this->folder . DIRECTORY_SEPARATOR .'*.jpeg,'
. $this->folder . DIRECTORY_SEPARATOR .'*.JPG,'
. $this->folder . DIRECTORY_SEPARATOR .'*.gif'
. '}', GLOB_BRACE);
shuffle($img_ar);
return $img_ar;
}
public function getContent() {
return $this->tpl;
}
}
?>