4 * Tool to create avatar images in different sizes.
6 * Part of Surrogator - a simple libravatar avatar image server
13 * @license http://www.gnu.org/licenses/agpl.html AGPLv3 or later
14 * @link https://sourceforge.net/p/surrogator/
17 $cfgFile = __DIR__ . '/data/surrogator.config.php';
18 if (!file_exists($cfgFile)) {
19 $cfgFile = '/etc/surrogator.config.php';
20 if (!file_exists($cfgFile)) {
22 "Configuration file does not exist.\n"
23 . "Copy data/surrogator.config.php.dist to data/surrogator.config.php"
32 foreach ($argv as $arg) {
33 if ($arg == '-v' || $arg == '--verbose') {
35 } else if ($arg == '-vv') {
37 } else if ($arg == '-q' || $arg == '--quiet') {
39 } else if ($arg == '-f' || $arg == '--force') {
41 } else if ($arg == '-h' || $arg == '--help') {
44 } else if ($arg == '--version') {
45 echo "surrogator 0.0.1\n";
47 } else if (file_exists($arg)) {
50 logErr('Unknown argument: ' . $arg);
56 * Echos the --help screen.
63 Usage: php surrogator.php [options] [filename(s)]
65 surrogator - a simple libravatar server
66 Put files in raw/ dir and run surrogator.php to generate different sizes
71 -v, --verbose Be verbose (more log messages, also -vv)
72 -q, --quiet Be quiet (no log messages)
73 -f, --force Force update of all files
74 --version Show program version
76 filenames One or several files whose small images shall get generated.
77 If none given, all will be checked
82 if (!isset($rawDir)) {
83 logErr('$rawDir not set');
86 if (!isset($varDir)) {
87 logErr('$varDir not set');
91 logErr('$sizes not set');
94 if (!isset($maxSize)) {
95 logErr('$maxSize not set');
98 if (!isset($logLevel)) {
99 logErr('$logLevel not set');
104 if (!is_dir($varDir . '/square')) {
105 log('creating square dir: ' . $varDir . '/square');
106 if (!mkdir($varDir . '/square', 0755, true)) {
107 logErr('cannot create square dir');
111 log('sizes: ' . implode(', ', $sizes), 2);
112 foreach ($sizes as $size) {
113 if (!is_dir($varDir . '/' . $size)) {
114 log('creating size dir: ' . $varDir . '/' . $size);
115 mkdir($varDir . '/' . $size, 0755);
118 foreach (array('mm.png', 'default.png') as $resFile) {
119 if (!file_exists($rawDir . '/' . $resFile)) {
120 log($resFile . ' missing, copying it from res/', 2);
121 copy($resDir . '/' . $resFile, $rawDir . '/' . $resFile);
124 foreach (array('index.html', 'robots.txt', 'favicon.ico') as $resFile) {
125 if (!file_exists($wwwDir . '/' . $resFile) && is_writable($wwwDir)) {
126 log('no www/' . $resFile . ' found, copying default over', 1);
127 copy($resDir . '/www/' . $resFile, $wwwDir . '/' . $resFile);
132 $fileInfos = array();
133 foreach ($files as $file) {
134 $fileInfos[] = new \SplFileInfo($file);
137 $fileInfos = new \RegexIterator(
138 new \DirectoryIterator($rawDir),
139 '#^.+\.(png|jpg|svg|svgz)$#'
142 foreach ($fileInfos as $fileInfo) {
143 $origPath = $fileInfo->getPathname();
144 $fileName = $fileInfo->getFilename();
145 $ext = strtolower(substr($fileName, strrpos($fileName, '.') + 1));
146 $squarePath = $varDir . '/square/'
147 . substr($fileName, 0, -strlen($ext)) . 'png';
149 log('processing ' . $fileName, 1);
150 if (imageUptodate($origPath, $squarePath)) {
151 log(' image up to date', 2);
155 if (!createSquare($origPath, $ext, $squarePath, $maxSize)) {
159 if ($fileName == 'default.png') {
160 $md5 = $sha256 = 'default';
161 } else if ($fileName == 'mm.png') {
162 $md5 = $sha256 = 'mm';
164 list($md5, $sha256) = getHashes($fileName);
167 log(' creating sizes for ' . $fileName, 2);
168 log(' md5: ' . $md5, 3);
169 log(' sha256: ' . $sha256, 3);
170 $imgSquare = imagecreatefrompng($squarePath);
171 foreach ($sizes as $size) {
172 log(' size ' . $size, 3);
173 $sizePathMd5 = $varDir . '/' . $size . '/' . $md5 . '.png';
174 $sizePathSha256 = $varDir . '/' . $size . '/' . $sha256 . '.png';
176 $imgSize = imagecreatetruecolor($size, $size);
177 imagealphablending($imgSize, false);
178 imagefilledrectangle(
179 $imgSize, 0, 0, $size - 1, $size - 1,
180 imagecolorallocatealpha($imgSize, 0, 0, 0, 127)
183 $imgSize, $imgSquare,
185 $size, $size, $maxSize, $maxSize
187 imagesavealpha($imgSize, true);
188 imagepng($imgSize, $sizePathMd5);
189 imagepng($imgSize, $sizePathSha256);
190 imagedestroy($imgSize);
193 imagedestroy($imgSquare);
197 * Create and return md5 and sha256 hashes from a filename.
201 * @return array Array with 2 values: md5 and sha256 hash
203 function getHashes($fileName)
205 //OpenIDs have their slashes "/" url-encoded
206 $fileName = rawurldecode($fileName);
208 $fileNameNoExt = pathinfo($fileName, PATHINFO_FILENAME);
209 $emailAddress = trim(strtolower($fileNameNoExt));
212 md5($emailAddress), hash('sha256', $emailAddress)
217 * Creates the square image from the given image in maximum size.
218 * Scales the image up or down and makes the non-covered parts transparent.
220 * @param string $origPath Full path to original image
221 * @param string $ext File extension ("jpg" or "png")
222 * @param string $targetPath Full path to target image file
223 * @param integer $maxSize Maxium image size the server supports
225 * @return boolean True if all went well, false if there was an error
227 function createSquare($origPath, $ext, $targetPath, $maxSize)
230 $imgOrig = imagecreatefrompng($origPath);
231 } else if ($ext == 'jpg' || $ext == 'jpeg') {
232 $imgOrig = imagecreatefromjpeg($origPath);
233 } else if ($ext == 'svg' || $ext == 'svgz') {
234 $imagickImg = new \Imagick();
235 $imagickImg->setBackgroundColor(new \ImagickPixel('transparent'));
236 $imagickImg->readImage($origPath);
237 $imagickImg->setImageFormat('png32');
238 $imgOrig = imagecreatefromstring($imagickImg->getImageBlob());
241 logErr('Unsupported image format: ' . $origPath);
245 if ($imgOrig === false) {
246 logErr('Error loading image file: ' . $origPath);
250 $imgSquare = imagecreatetruecolor($maxSize, $maxSize);
251 imagealphablending($imgSquare, false);
252 imagefilledrectangle(
253 $imgSquare, 0, 0, $maxSize - 1, $maxSize - 1,
254 imagecolorallocatealpha($imgSquare, 0, 0, 0, 127)
256 imagealphablending($imgSquare, true);
258 $oWidth = imagesx($imgOrig);
259 $oHeight = imagesy($imgOrig);
260 if ($oWidth > $oHeight) {
261 $flScale = $maxSize / $oWidth;
263 $flScale = $maxSize / $oHeight;
265 $nWidth = (int)($oWidth * $flScale);
266 $nHeight = (int)($oHeight * $flScale);
269 $imgSquare, $imgOrig,
270 ($maxSize - $nWidth) / 2, ($maxSize - $nHeight) / 2,
276 imagesavealpha($imgSquare, true);
277 imagepng($imgSquare, $targetPath);
279 imagedestroy($imgSquare);
280 imagedestroy($imgOrig);
285 * Check if the target file is newer than the source file.
287 * @param string $sourcePath Full source file path
288 * @param string $targetPath Full target file path
290 * @return boolean True if target file is newer than the source file
292 function imageUptodate($sourcePath, $targetPath)
298 if (!file_exists($targetPath)) {
301 if (filemtime($sourcePath) > filemtime($targetPath)) {
310 * Write a log message to stdout
312 * @param string $msg Message to write
313 * @param integer $level Log level - 1 is important, 3 is unimportant
317 function log($msg, $level = 1)
320 if ($level <= $logLevel) {
326 * Write an error message to stderr
328 * @param string $msg Message to write
332 function logErr($msg)
334 file_put_contents('php://stderr', $msg . "\n");