Commit dc5ab7c4 authored by Aroop "FinlayDaG33k" Roelofs's avatar Aroop "FinlayDaG33k" Roelofs
Browse files

Optimize blog images

parent 376c9aa4
......@@ -13,14 +13,11 @@ RUN docker-php-ext-install intl
# Add PDO
RUN docker-php-ext-install pdo pdo_mysql
# Add GD
# Add ImageMagick
RUN apt-get install -y \
libpng-dev libwebp-dev libjpeg-dev libz-dev libxpm-dev
RUN docker-php-ext-configure gd \
--with-jpeg \
--with-xpm \
--with-webp
RUN docker-php-ext-install gd
libmagickwand-dev --no-install-recommends \
&& pecl install imagick \
&& docker-php-ext-enable imagick
# Add APCu
RUN pecl install apcu && docker-php-ext-enable apcu
......
......@@ -5,6 +5,7 @@
use Cake\Http\Exception\NotFoundException;
use Cake\I18n\Time;
use Cake\Filesystem\File;
use Cake\Core\Configure;
class BlogController extends AppController {
public function initialize() {
......@@ -19,6 +20,7 @@
// Load the required components
$this->loadComponent('Paginator');
$this->loadComponent('Blog');
$this->loadComponent('Privacy');
}
public function display($query = null) {
......@@ -96,6 +98,16 @@
// Pass our view vars
$this->set(compact('article'));
$this->set('body', $file->read());
$this->set('shortcodeCache', 'blog-rendered-' . $article->hash);
if(!Configure::read('debug')) {
$userSettings = $this->Privacy->getSettings();
if($userSettings['ipfs']) {
$this->set('shortcodeCache', 'blog-rendered-no-ipfs-' . $article->hash);
} else {
$this->set('shortcodeCache', 'blog-rendered-with-ipfs-' . $article->hash);
}
} else {
$this->set('shortcodeCache', null);
}
}
}
\ No newline at end of file
......@@ -16,13 +16,13 @@
);
// Read our image
$data = file_get_contents($job->data('outDir') . $job->data('src') . ".png");
$data = file_get_contents($job->data('outDir') . $job->data('src'));
// Upload the image to IPFS
$res = static::$ipfs->add($data);
// Store a reference to the image
$ref = new File($job->data('outDir') . $job->data('src') . ".png.ipfs", true);
$ref = new File($job->data('outDir') . $job->data('src') . ".ipfs", true);
$ref->write($res, 'w', true);
}
}
\ No newline at end of file
<div class="row mb-2">
<div class="col-5">
<div class="col col-sm-12" style="max-width: <?= $dimensions['max-width']; ?>px;">
<div class="card rounded-0">
<a href="<?= $url; ?>" target="_blank">
<img class="card-img-top rounded-0" data-src="<?= $url; ?>">
<a href="<?= $urls['full']; ?>" target="_blank">
<img class="card-img-top rounded-0" data-src="<?= $urls['full']; ?>" data-srcset="<?= $urls['set']; ?>">
<div class="img-loader text-center" style="height: <?= $dimensions['max-height']; ?>px;">
<?= $this->element('sk-cube-spinner'); ?>
<br />
......
......@@ -5,15 +5,21 @@ use Cake\View\Cell;
use Cake\Routing\Router;
use Cake\Filesystem\File;
use Josegonzalez\CakeQueuesadilla\Queue\Queue;
use \Imagick;
class ImgShortcodeCell extends Cell {
private string $cache_dir = WWW_ROOT . "img/cache/";
private string $watermark_file = WWW_ROOT . 'img/logo-FDG-300-01-300x300.png';
private array $params = [];
private array $thumbnailWidths = [250, 450];
private string $content = '';
private string $srcUrlHash = '';
private bool $useIpfs = true;
private string $ipfsHost = '';
public function display(array $params, string $content) {
$this->params = $params;
$this->ipfsHost = env('IPFS_PUBLIC_ROOT', 'https://ipfs.finlaydag33k.nl/ipfs/');
// Check if the content is empty
// If not, store that in a property
......@@ -24,6 +30,9 @@ class ImgShortcodeCell extends Cell {
$this->content = $params['src'];
}
// Hash our src url
$this->srcUrlHash = sha1($this->content);
// Make sure the cachedir exists
// If not, create it
if(!file_exists($this->cache_dir)) mkdir($this->cache_dir);
......@@ -32,114 +41,231 @@ class ImgShortcodeCell extends Cell {
// If so, don't watermark our image
// If not, watermark it
if(!empty($this->params['source'])) {
if(!file_exists($this->cache_dir . sha1($this->content) . ".png")) $this->store($this->content);
if(!file_exists($this->cache_dir . $this->srcUrlHash . ".png")) $this->store();
} else {
if(!file_exists($this->cache_dir . sha1($this->content) . ".png")) $this->watermarkImage($this->content);
if(!file_exists($this->cache_dir . $this->srcUrlHash . ".png")) $this->watermarkImage();
}
// Check whether this image exists on IPFS
// If not, upload it and store a reference
if(!file_exists($this->cache_dir . sha1($this->content) . ".png.ipfs")) {
if(!file_exists($this->cache_dir . $this->srcUrlHash . ".png.ipfs")) {
Queue::push('\App\Job\IPFSImageUploadJob::run', [
'src' => sha1($this->content),
'src' => $this->srcUrlHash . ".png",
'outDir' => $this->cache_dir,
]);
}
// Get the dimensions of the image
list($width, $height) = getimagesize($this->cache_dir . sha1($this->content) . ".png");
list($width, $height) = getimagesize($this->cache_dir . $this->srcUrlHash . ".png");
// Calculate the max-height of the image
$maxHeight = ($height / $width) * 445;
$maxHeight = ($height / $width) * 450;
// Calculate the max-width of the card
$maxWidth = ($width > 450) ? 450 : $width;
// Create thumbnails
$this->createThumbnails();
// Check if the user enabled IPFS
$userSettings = json_decode($this->request->getCookie('userSettings'), true);
$this->useIpfs = $userSettings['ipfs'];
// Pass our view variables
$this->set('caption', !empty($this->params['caption']) ? $this->params['caption'] : null);
$this->set('url', $this->buildImageUrl($this->content));
$this->set('urls', $this->buildImageUrls());
$this->set('source', !empty($this->params['source']) ? $this->params['source'] : null);
$this->set('linkonly', isset($this->params['linkonly']) ? $this->params['linkonly'] : false);
$this->set('dimensions', [
'width' => $width,
'height' => $height,
'max-height' => $maxHeight,
'max-width' => $maxWidth,
]);
}
private function buildImageUrl($src){
// Check if the user enabled IPFS
$userSettings = json_decode($this->request->getCookie('userSettings'), true);
if(!$userSettings['ipfs']) {
return Router::fullBaseUrl() . "/img/cache/" . sha1($src) . ".png";
private function buildImageUrls(){
if(!$this->useIpfs) {
return [
'full' => Router::fullBaseUrl() . "/img/cache/" . $this->srcUrlHash . ".png",
'set' => $this->getSrcSet(),
];
}
// Check if we have a hash of the file
// If not, return a regular URL
if(!file_exists($this->cache_dir . sha1($this->content) . ".png.ipfs")) {
return Router::fullBaseUrl() . "/img/cache/" . sha1($src) . ".png";
if(!file_exists($this->cache_dir . $this->srcUrlHash . ".png.ipfs")) {
return [
'full' => Router::fullBaseUrl() . "/img/cache/" . $this->srcUrlHash . ".png",
'set' => $this->getSrcSet(),
];
}
// Get the hash from the file
$hash = file_get_contents($this->cache_dir . sha1($this->content) . ".png.ipfs");
// Get the url from IPFS
// If no url is found, use a regular URL for now
// Do check the srcset for IPFS urls
$ipfsUrl = $this->getIpfsUrl($this->cache_dir . $this->srcUrlHash . ".png.ipfs");
if(!$ipfsUrl) {
return [
'full' => Router::fullBaseUrl() . "/img/cache/" . $this->srcUrlHash . ".png",
'set' => $this->getSrcSet(),
];
}
// Check if there is content
// If not, remove the reference and return a regular URL for now
if(empty($hash)) {
unlink($this->cache_dir . sha1($this->content) . ".png.ipfs");
return Router::fullBaseUrl() . "/img/cache/" . sha1($src) . ".png";
// Return the IPFS url
return [
'full' => $this->ipfsHost . $ipfsUrl,
'set' => $this->getSrcSet(),
];
}
private function getSrcSet() {
$set = '';
$last = end($this->thumbnailWidths);
foreach($this->thumbnailWidths as $width) {
// Check whether we want to use IPFS
if($this->useIpfs) {
// Make sure we have an IPFS url
$ipfsUrl = $this->getIpfsUrl($this->cache_dir . $this->srcUrlHash . "-thumb-".$width.".jpeg.ipfs");
if($ipfsUrl) {
$set .= $this->ipfsHost;
$set .= $ipfsUrl;
$set .= ' ';
$set .= $width;
$set .= 'w';
if($width !== $last) $set .= ', ';
continue;
} else {
Queue::push('\App\Job\IPFSImageUploadJob::run', [
'src' => $this->srcUrlHash . "-thumb-".$width.".jpeg",
'outDir' => $this->cache_dir,
]);
}
}
if(file_exists($this->cache_dir . $this->srcUrlHash . "-thumb-".$width.".jpeg")) {
$set .= Router::fullBaseUrl() . "/img/cache/" . $this->srcUrlHash . "-thumb-".$width.".jpeg";
$set .= ' ';
$set .= $width;
$set .= 'w';
if($width !== $last) $set .= ', ';
}
}
return $set;
}
// Get the IPFS gateway host
$url = env('IPFS_PUBLIC_ROOT', 'https://ipfs.finlaydag33k.nl/ipfs/');
private function getIpfsUrl(string $ref) {
// Make sure the reference exists
if(!file_exists($ref)) return null;
// Return the IPFS url
return $url . $hash;
// Get the hash from the file
$hash = file_get_contents($ref);
// Check whether there is a hash
// If not, remove the reference
if(empty($hash)) {
unlink($ref);
return null;
}
return $hash;
}
private function watermarkImage(string $src){
private function watermarkImage(){
// Create a temporary file
$tmpfname = tempnam("/tmp", "img_");
// Download our image into the temporary file
$img = file_get_contents($src);
file_put_contents($tmpfname, $img);
file_put_contents($tmpfname, file_get_contents($this->content));
// Load our temporary file
$im = new Imagick($tmpfname);
// Load the watermark
$stamp = imagecreatefrompng($this->watermark_file);
$im = imagecreatefromstring(file_get_contents($tmpfname)); // Let's just have PHP handle the type detection
$watermark = new Imagick($this->watermark_file);
// Get the height and width of the image
$imHeight = $im->getImageHeight();
$imWidth = $im->getImageWidth();
// Get the height and width of the watermark
$markHeight = $watermark->getImageHeight();
$markWidth = $watermark->getImageWidth();
// Calculate the sizes for the watermark
if(imagesx($im) > imagesy($im)){
// if the width of the main image is bigger than the height
$newStampDimensions = [(imagesx($im) / 100) * 6.25,(imagesx($im) / 100) * 6.25];
// If the width of the main image is bigger than the height:
// - Divide the mark width by 100
// - Multiply by 6.25
// Else:
// - Divide the mark height by 100
// - Multiply by 12.5
if($imWidth > $imHeight){
$markDimensions = [
'height' => ($imWidth / 100) * 6.25,
'width' => ($imWidth / 100) * 6.25
];
}else{
// if the height of the main image is bigger than the width
$newStampDimensions = [(imagesy($im) / 100) * 12.5,(imagesy($im) / 100) * 12.5];
$markDimensions = [
'height' => ($imHeight / 100) * 12.5,
'width' => ($imHeight / 100) * 12.5
];
}
// Resize the watermark
$newStamp = imagecreatetruecolor($newStampDimensions[0], $newStampDimensions[1]);
imagealphablending($newStamp, false);
imagesavealpha($newStamp,true);
imagecopyresampled($newStamp, $stamp, 0, 0, 0, 0, $newStampDimensions[0], $newStampDimensions[1], imagesx($stamp), imagesy($stamp));
// Then update the object
$watermark->scaleImage($markDimensions['width'], $markDimensions['height']);
$markHeight = $watermark->getImageHeight();
$markWidth = $watermark->getImageWidth();
// Calculate the watermark position
$loc_right = imagesx($im) - imagesx($newStamp);
$loc_bottom = imagesy($im) - imagesy($newStamp);
$x = $imWidth - $markWidth;
$y = $imHeight - $markHeight;
// Add the watermark to the image based on everything we've just calculated
imagecopy($im, $newStamp, $loc_right, $loc_bottom, 0, 0, imagesx($newStamp), imagesy($newStamp));
$im->compositeImage($watermark, Imagick::COMPOSITE_OVER, $x, $y);
// Save the image in our cache dir
imagepng($im, $this->cache_dir . sha1($src) . ".png");
// Clean up just for the sake off
imagedestroy($im);
imagedestroy($stamp);
imagedestroy($newStamp);
file_put_contents($this->cache_dir . $this->srcUrlHash . ".png", $im);
}
private function createThumbnail(int $targetWidth) {
// Load the image
$im = new Imagick($this->cache_dir . $this->srcUrlHash . ".png");
// Turn into JPEG
$im->setImageFormat('jpeg');
// Compression
$im->setImageCompression(Imagick::COMPRESSION_JPEG);
// Set image quality
$im->setImageCompressionQuality(60);
// Check if our width is higher than our target
// If so, rescale
if($im->getImageWidth() > $targetWidth) $im->thumbnailImage($targetWidth, 0);
// Save the image in our cache dir
file_put_contents($this->cache_dir . $this->srcUrlHash . "-thumb-".$targetWidth.".jpeg", $im);
}
private function createThumbnails() {
foreach($this->thumbnailWidths as $width) {
if(!file_exists($this->cache_dir . $this->srcUrlHash . "-thumb-".$width.".jpeg")) {
$this->createThumbnail($width);
Queue::push('\App\Job\IPFSImageUploadJob::run', [
'src' => $this->srcUrlHash . "-thumb-".$width.".jpeg",
'outDir' => $this->cache_dir,
]);
}
}
}
public function store(string $src) {
public function store() {
// Get our image content
$img = file_get_contents($src);
$img = file_get_contents($this->content);
// Create a temporary file
// Store our image in it
......@@ -150,6 +276,6 @@ class ImgShortcodeCell extends Cell {
$png = imagecreatefromstring(file_get_contents($tmpfname));
// Save the image in our cache dir
imagepng($png, $this->cache_dir . sha1($src) . ".png");
imagepng($png, $this->cache_dir . $this->srcUrlHash . ".png");
}
}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment