Created
May 26, 2017 07:55
-
-
Save voduytuan/4a46e2ba5dcb353e0f60bdc483b1a5f3 to your computer and use it in GitHub Desktop.
Generate LQIP (Low quality image placeholder) from PHP
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
//example run: from command line: > php generator.php | |
include ('ImageResizer.php'); | |
$inputFile = 'sample.png'; | |
$outputFile = 'small_' . $inputFile; | |
$resizer = new \Litpi\ImageResizer( | |
'./', | |
$inputFile, | |
'./', | |
$outputFile, | |
50, | |
50, | |
'', | |
20 | |
); | |
//capture the image data | |
ob_start(); | |
$resizer->output(false); | |
$imageContent = ob_get_clean(); | |
//format image data to base64 | |
$type = pathinfo($outputFile, PATHINFO_EXTENSION); | |
$base64 = 'data:image/' . $type . ';base64,' . base64_encode($imageContent); | |
//////////////////////////// | |
//replace data of index.html | |
$htmlPath = 'index.html'; | |
$content = file_get_contents($htmlPath); | |
$content = preg_replace('/src="(.*?)"/i', 'src="'.$base64.'"', $content); | |
if ($content != '') { | |
file_put_contents($htmlPath, $content); | |
} | |
echo $content; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Litpi; | |
define('MEMORY_TO_ALLOCATE', '100M'); | |
/** | |
* Resize image | |
* - provide crop to square size image | |
* - provide 4 corner to create round corner image (advanced) | |
*/ | |
class ImageResizer | |
{ | |
private $sourceFolder = ''; | |
private $sourceName = ''; | |
private $destFolder = ''; | |
private $destName = ''; | |
private $maxWidth = 0; | |
private $maxHeight = 0; | |
private $cropRatio = ''; | |
private $watermarkFile = ''; | |
private $watermarkPosition = 'topleft'; //eight position | |
private $watermarkPadding = array( | |
'top' => 0, | |
'right' => 0, | |
'bottom' => 0, | |
'left' => 0 | |
); | |
private $corner = false; | |
private $cornerImages = array(); | |
private $size = array(); | |
private $quality = 90; | |
private $color = ''; | |
/** | |
* Construction for class | |
* | |
* @param string $sourceFolder : end with slash '/' | |
* @param string $sourceName | |
* @param string $destFolder : end with slash '/' | |
* @param string $destName | |
* @param int $maxWidth | |
* @param int $maxHeight | |
* @param string $cropRatio | |
* @param int $quality | |
* @param string $color | |
* | |
* @return ImageResizer | |
*/ | |
public function __construct($sourceFolder, $sourceName, $destFolder, $destName, $maxWidth = 0, $maxHeight = 0, $cropRatio = '', $quality = 90, $color = 'ffffff') | |
{ | |
$this->sourceFolder = $sourceFolder; | |
$this->sourceName = $sourceName; | |
$this->destFolder = $destFolder; | |
$this->destName = $destName; | |
$this->maxWidth = $maxWidth; | |
$this->maxHeight = $maxHeight; | |
$this->cropRatio = $cropRatio; | |
$this->quality = $quality; | |
$this->color = $color; | |
if (!file_exists($this->sourceFolder . $this->sourceName)) { | |
echo 'Error: image does not exist: ' . $this->sourceFolder . $this->sourceName; | |
return null; | |
} | |
$size = GetImageSize($this->sourceFolder . $this->sourceName); | |
$mime = $size['mime']; | |
// Make sure that the requested file is actually an image | |
if (substr($mime, 0, 6) != 'image/') { | |
echo 'Error: requested file is not an accepted type: ' . $this->sourceFolder . $this->sourceName; | |
return null; | |
} | |
$this->size = $size; | |
if ($color != '') { | |
$this->color = preg_replace('/[^0-9a-fA-F]/', '', $color); | |
} else { | |
$this->color = false; | |
} | |
} | |
/** | |
* Assign watermark to output image | |
* | |
* @param string $watermarkFile | |
* @param string $watermarkPosition | |
* @param string/array $watermarkPadding | |
*/ | |
public function addWatermark($watermarkFile, $watermarkPosition = '', $watermarkPadding = '') | |
{ | |
$this->watermarkFile = $watermarkFile; | |
if (in_array($watermarkPosition, array( | |
'center', | |
'topleft', | |
'topcenter', | |
'topright', | |
'rightmiddle', | |
'bottomright', | |
'bottomcenter', | |
'bottomleft', | |
'leftmiddle' | |
))) { | |
$this->watermarkPosition = $watermarkPosition; | |
} | |
if (is_array($watermarkPadding)) { | |
$this->watermarkPadding = $watermarkPadding; | |
} elseif (strlen($watermarkPadding) > 0) { | |
$paddingArray = split(',', $watermarkPadding); | |
if (isset($paddingArray[0])) { | |
$this->watermarkPadding['top'] = (int) ($paddingArray[0]); | |
} | |
if (isset($paddingArray[1])) { | |
$this->watermarkPadding['right'] = (int) ($paddingArray[1]); | |
} | |
if (isset($paddingArray[2])) { | |
$this->watermarkPadding['bottom'] = (int) ($paddingArray[2]); | |
} | |
if (isset($paddingArray[3])) { | |
$this->watermarkPadding['left'] = (int) ($paddingArray[3]); | |
} | |
} | |
} | |
/** | |
* Assign corner image to 4 corners of image | |
* | |
* @param array $cornerImages : assoc array ('topleft' => PNG image, 'topright' => PNG image, 'bottomright' => PNG image, 'bottomleft' => PNG image) | |
*/ | |
public function addCorner($cornerImages = array()) | |
{ | |
$this->corner = true; | |
$this->cornerImages = $cornerImages; | |
} | |
/** | |
* Set the quality of the output image | |
* | |
* @param mixed $quality | |
*/ | |
public function setQuality($quality = 90) | |
{ | |
$this->quality = $quality; | |
} | |
/** | |
* Output the image | |
* | |
* @param boolean $save : If true: save image to selected destFolder and destName, otherwise, output image to browser with encoding of image | |
*/ | |
public function output($save = true) | |
{ | |
$width = $this->size[0]; | |
$height = $this->size[1]; | |
$mime = $this->size['mime']; | |
$color = $this->color; | |
// If we don't have a max width or max height, OR the image is smaller than both | |
// we do not want to resize it, so we simply output the original image and exit | |
// Exection: in case caller want to add watermark only, still create new image... | |
if (((!$this->maxWidth && !$this->maxHeight) || ($this->maxWidth >= $width && $this->maxHeight >= $height)) && $this->watermarkFile == '') { | |
if ($save) { | |
copy($this->sourceFolder . $this->sourceName, $this->destFolder . $this->destName); | |
} else { | |
$data = file_get_contents($this->sourceFolder . $this->sourceName); | |
header("Content-type: " . $this->size['mime'] . ""); | |
header('Content-Length: ' . strlen($data)); | |
echo $data; | |
} | |
} else { | |
//need to resize image | |
// Ratio cropping | |
$offsetX = 0; | |
$offsetY = 0; | |
if (strlen($this->cropRatio) > 0) { | |
$cropRatio = explode(':', $this->cropRatio); | |
if (count($cropRatio) == 2) { | |
$ratioComputed = $width / $height; | |
$cropRatioComputed = (float) $cropRatio[0] / (float) $cropRatio[1]; | |
if ($ratioComputed < $cropRatioComputed) { // Image is too tall so we will crop the top and bottom | |
$origHeight = $height; | |
$height = $width / $cropRatioComputed; | |
$offsetY = ($origHeight - $height) / 2; | |
} elseif ($ratioComputed > $cropRatioComputed) { // Image is too wide so we will crop off the left and right sides | |
$origWidth = $width; | |
$width = $height * $cropRatioComputed; | |
$offsetX = ($origWidth - $width) / 2; | |
} | |
} | |
} | |
// Setting up the ratios needed for resizing. We will compare these below to determine how to | |
// resize the image (based on height or based on width) | |
$xRatio = $this->maxWidth / $width; | |
$yRatio = $this->maxHeight / $height; | |
if ($xRatio > 1 && $yRatio > 1) { | |
$tnWidth = $width; | |
$tnHeight = $height; | |
} else { | |
if ($xRatio * $height < $this->maxHeight) { // Resize the image based on width | |
$tnHeight = ceil($xRatio * $height); | |
$tnWidth = $this->maxWidth; | |
} else { | |
$tnWidth = ceil($yRatio * $width); | |
$tnHeight = $this->maxHeight; | |
} | |
} | |
// Determine the quality of the output image | |
$quality = $this->quality; | |
// We don't want to run out of memory | |
ini_set('memory_limit', MEMORY_TO_ALLOCATE); | |
// Set up a blank canvas for our resized image (destination) | |
$dst = imagecreatetruecolor($tnWidth, $tnHeight); | |
// Set up the appropriate image handling functions based on the original image's mime type | |
switch ($this->size['mime']) { | |
case 'image/gif': | |
// We will be converting GIFs to PNGs to avoid transparency issues when resizing GIFs | |
// This is maybe not the ideal solution, but IE6 can suck it | |
$creationFunction = 'ImageCreateFromGif'; | |
$outputFunction = 'ImagePng'; | |
$mime = 'image/png'; // We need to convert GIFs to PNGs | |
$doSharpen = false; | |
$quality = round(10 - ($quality / 10)); // We are converting the GIF to a PNG and PNG needs a compression level of 0 (no compression) through 9 | |
break; | |
case 'image/x-png': | |
case 'image/png': | |
$creationFunction = 'ImageCreateFromPng'; | |
$outputFunction = 'ImagePng'; | |
$doSharpen = false; | |
$quality = round(10 - ($quality / 10)); // PNG needs a compression level of 0 (no compression) through 9 | |
break; | |
default: | |
$creationFunction = 'ImageCreateFromJpeg'; | |
$outputFunction = 'ImageJpeg'; | |
$doSharpen = true; | |
break; | |
} | |
// Read in the original image | |
$src = $creationFunction($this->sourceFolder . $this->sourceName); | |
imagealphablending($src, true); | |
if (in_array($this->size['mime'], array( | |
'image/gif', | |
'image/png' | |
))) { | |
if (!$color) { | |
// If this is a GIF or a PNG, we need to set up transparency | |
imagealphablending($dst, true); | |
imagesavealpha($dst, true); | |
} else { | |
// Fill the background with the specified color for matting purposes | |
if ($color[0] == '#') { | |
$color = substr($color, 1); | |
} | |
$background = false; | |
if (strlen($color) == 6) { | |
$background = imagecolorallocate($dst, hexdec($color[0] . $color[1]), hexdec($color[2] . $color[3]), hexdec($color[4] . $color[5])); | |
} elseif (strlen($color) == 3) { | |
$background = imagecolorallocate($dst, hexdec($color[0] . $color[0]), hexdec($color[1] . $color[1]), hexdec($color[2] . $color[2])); | |
} | |
if ($background) { | |
imagefill($dst, 0, 0, $background); | |
} | |
} | |
} | |
// Resample the original image into the resized canvas we set up earlier | |
ImageCopyResampled($dst, $src, 0, 0, $offsetX, $offsetY, $tnWidth, $tnHeight, $width, $height); | |
if ($doSharpen) { | |
// Sharpen the image based on two things: | |
// (1) the difference between the original size and the final size | |
// (2) the final size | |
$sharpness = $this->findSharp($width, $tnWidth); | |
$sharpenMatrix = array( | |
array(-1, -2, -1 | |
) , | |
array(-2, | |
$sharpness + 12, -2 | |
) , | |
array(-1, -2, -1 | |
) | |
); | |
$divisor = $sharpness; | |
$offset = 0; | |
imageconvolution($dst, $sharpenMatrix, $divisor, $offset); | |
} | |
//process add watermark | |
if ($this->watermarkFile != '' && file_exists($this->watermarkFile)) { | |
$watermarkImage = imagecreatefrompng($this->watermarkFile); | |
list($watermarkWidth, $watermarkHeight) = getimagesize($this->watermarkFile); | |
imagealphablending($watermarkImage, true); | |
imagesavealpha($watermarkImage, true); | |
$dst_x = (int) ($this->watermarkPadding['left']) - (int) ($this->watermarkPadding['right']); | |
$dst_y = (int) ($this->watermarkPadding['top']) - (int) ($this->watermarkPadding['bottom']); | |
switch ($this->watermarkPosition) { | |
case 'center': | |
$dst_x+= (int) ($tnWidth / 2 - $watermarkWidth / 2); | |
$dst_y+= (int) ($tnHeight / 2 - $watermarkHeight / 2); | |
break; | |
case 'topleft': | |
$dst_x+= 0; | |
$dst_y+= 0; | |
break; | |
case 'topcenter': | |
$dst_x+= (int) ($tnWidth / 2 - $watermarkWidth / 2); | |
$dst_y+= 0; | |
break; | |
case 'topright': | |
$dst_x+= $tnWidth - $watermarkWidth; | |
$dst_y+= 0; | |
break; | |
case 'rightmiddle': | |
$dst_x+= $tnWidth - $watermarkWidth; | |
$dst_y+= (int) ($tnHeight / 2 - $watermarkHeight / 2); | |
break; | |
case 'bottomright': | |
$dst_x+= $tnWidth - $watermarkWidth; | |
$dst_y+= $tnHeight - $watermarkHeight; | |
break; | |
case 'bottomcenter': | |
$dst_x+= (int) ($tnWidth / 2 - $watermarkWidth / 2); | |
$dst_y+= $tnHeight - $watermarkHeight; | |
break; | |
case 'bottomleft': | |
$dst_x+= 0; | |
$dst_y+= $tnHeight - $watermarkHeight; | |
break; | |
case 'leftmiddle': | |
$dst_x+= 0; | |
$dst_y+= (int) ($tnHeight / 2 - $watermarkHeight / 2); | |
break; | |
} | |
imagecopy($dst, $watermarkImage, $dst_x, $dst_y, 0, 0, $watermarkWidth, $watermarkHeight); | |
} | |
//process add corner to image | |
if ($this->corner) { | |
foreach ($this->cornerImages as $position => $filenameCorner) { | |
if (file_exists($filenameCorner)) { | |
list($cornerWidth, $cornerHeight) = getimagesize($filenameCorner); | |
$corner = imagecreatefrompng($filenameCorner); | |
$dst_x = 0; | |
$dst_y = 0; | |
switch ($position) { | |
case 'topleft': | |
$dst_x = 0; | |
$dst_y = 0; | |
break; | |
case 'topright': | |
$dst_x = $tnWidth - $cornerWidth; | |
$dst_y = 0; | |
break; | |
case 'bottomright': | |
$dst_x = $tnWidth - $cornerWidth; | |
$dst_y = $tnHeight - $cornerHeight; | |
break; | |
case 'bottomleft': | |
$dst_x = 0; | |
$dst_y = $tnHeight - $cornerHeight; | |
break; | |
} | |
imagecopy($dst, $corner, $dst_x, $dst_y, 0, 0, $cornerWidth, $cornerHeight); | |
} | |
} | |
} | |
if ($save) { | |
if (!file_exists($this->destFolder)) { | |
mkdir($this->destFolder, 0777, true); | |
} | |
// Write the resized image to the destination | |
$outputFunction($dst, $this->destFolder . $this->destName, $quality); | |
} else { | |
//output image to browser | |
header('Content-Type: ' . $mime); | |
$outputFunction($dst, null, $quality); | |
} | |
} | |
} | |
// function from Ryan Rud (http://adryrun.com) | |
private function findSharp($orig, $final) | |
{ | |
$final = $final * (750.0 / $orig); | |
$a = 52; | |
$b = - 0.27810650887573124; | |
$c = .00047337278106508946; | |
$result = $a + $b * $final + $c * $final * $final; | |
return max(round($result), 0); | |
} // findSharp() | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head><title>LQIP Sample</title></head> | |
<body> | |
<img src="" width="877" height="380" /> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment