Hi,
We are using imagick for different image manipulations and have a request to add
QR watermarks in the end.
Right now I could only find PHP QR Code library which uses the GD2 library:
Implemented purely in PHP, no external dependencies except GD2
Is there any php snippet or library which uses imagick to create QR codes?
Looking at the PHP QR Code library, there is only one file (I think) that accesses the GD library: qrimage.php. So change that file to output via imagick and use the rest of PHP QR Code.
Below is a possible imagick output file I wrote to replace qrimage.php. However, I am unable to test this code, since I am on Windows, and cannot install imagick.
Can someone please debug it, and edit this post with any corrections?
<?php
/*
* PHP QR Code encoder
*
* Image output of code using GD2
*
* PHP QR Code is distributed under LGPL 3
* Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
define('QR_IMAGE', true);
class QRimage {
//----------------------------------------------------------------------
public static function png($frame, $filename = false, $pixelPerPoint = 4, $outerFrame = 4,$saveandprint=FALSE)
{
$image = self::image($frame, $pixelPerPoint, $outerFrame, "png", 85, $filename, $saveandprint);
}
//----------------------------------------------------------------------
public static function jpg($frame, $filename = false, $pixelPerPoint = 8, $outerFrame = 4, $q = 85)
{
$image = self::image($frame, $pixelPerPoint, $outerFrame, "jpeg", $q, $filename, $saveandprint);
}
//----------------------------------------------------------------------
private static function image($frame, $pixelPerPoint = 4, $outerFrame = 4,
$format = "png", $quality = 85, $filename = FALSE, $saveandprint = FALSE)
{
$imgH = count($frame);
$imgW = strlen($frame[0]);
$col[0] = new ImagickPixel("white");
$col[1] = new ImagickPixel("black");
$image = new Imagick();
$image->newImage($imgW, $imgH, $col[0]);
$image->setCompressionQuality($quality);
$image->setImageFormat($format);
$draw = new ImagickDraw();
$draw->setFillColor($col[1]);
for($y=0; $y<$imgH; $y++) {
for($x=0; $x<$imgW; $x++) {
if ($frame[$y][$x] == '1') {
$draw->point($x,$y);
}
}
}
$image->drawImage($draw);
$image->borderImage($col[0],$outerFrame,$outerFrame);
$image->scaleImage( $imgW * $pixelPerPoint, 0 );
if ($filename === FALSE) {
Header("Content-type: image/jpeg");
echo $image;
} else {
if($saveandprint===TRUE){
$image->writeImages($filename, true);
Header("Content-type: image/" . $format);
echo $image;
} else {
echo $image;
}
}
}
}
There is a merged file called phpqrcode.php that contains the entire qrimage.php, so you will either have to remerge that file, or else replace the relevant section.
If you use a different filename for the above code, you will have to change the reference in the file qrlib.php and merge.php.
I tested the above implementation and it worked.
There is one mistake : you missed to add the outerframe in the final image size.
$image->scaleImage( ($imgW + 2*$outerFrame) * $pixelPerPoint, 0 );
Also, it seems that GD library is a lot faster than ImageMagick in this case.
I benched the use of GD and imagick on the creation of the same 50 random qr codes.
I isolated the part were QR code are generated so in fact the use of QRimage::png.
I only teste png generation.
These are my results :
GD :
min time : 0,0148401260 s
max time : 0,0211210251 s
average : 0,0167747593 s
ImageMagick :
min time : 0,0799968243 s
max time : 0,1147611141 s
average : 0,0918840790 s
In the final code, this makes a little difference. The other part of the code takes something like 0.15s to run and on a large amount of codes it makes a difference (I benched QRcode::png with a result like : 0.17s per qrcode ith GD and 0.24s per code with imagemagick).
Related
I am currently trying to get tcpdf working on my website. I am using xampp as a localhost, but cannot seem to get the config files working correctly. I am almost certain my paths are pointing to the right directory in tcpdf_config.php but when I try and run a script on my email.php page, it is displaying the error shown below. I have tried playing around with pointing to the file/s in a number of ways, but to no avail.
When i navigate to the examples given in the TCPDF folder, they load and work fine, so not sure why i cannot get this page work. Any help much appreciated!
i have tcpdf installed in my xampp directory as follows:
C:/xampp/htdocs/projects/ibill_v3/tcpdf
tcpdf_config.php
<?php
//============================================================+
// File name : tcpdf_config.php
// Begin : 2004-06-11
// Last Update : 2014-12-11
//
// Description : Configuration file for TCPDF.
// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info#tecnick.com
// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
// -------------------------------------------------------------------
// Copyright (C) 2004-2014 Nicola Asuni - Tecnick.com LTD
//
// This file is part of TCPDF software library.
//
// TCPDF is free software: you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// TCPDF is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with TCPDF. If not, see <http://www.gnu.org/licenses/>.
//
// See LICENSE.TXT file for more information.
//============================================================+
/**
* Configuration file for TCPDF.
* #author Nicola Asuni
* #package com.tecnick.tcpdf
* #version 4.9.005
* #since 2004-10-27
*/
// IMPORTANT:
// If you define the constant K_TCPDF_EXTERNAL_CONFIG, all the following settings will be ignored.
// If you use the tcpdf_autoconfig.php, then you can overwrite some values here.
/**
* Installation path (/var/www/tcpdf/).
* By default it is automatically calculated but you can also set it as a fixed string to improve performances.
*/
define ('K_PATH_MAIN', 'C:/xampp/htdocs/projects/ibill_v3/tcpdf');
/**
* URL path to tcpdf installation folder (http://localhost/tcpdf/).
* By default it is automatically set but you can also set it as a fixed string to improve performances.
*/
define ('K_PATH_URL', 'C:/xampp/htdocs/projects/ibill_v3/tcpdf');
/**
* Path for PDF fonts.
* By default it is automatically set but you can also set it as a fixed string to improve performances.
*/
//define ('K_PATH_FONTS', K_PATH_MAIN.'fonts/');
/**
* Default images directory.
* By default it is automatically set but you can also set it as a fixed string to improve performances.
*/
define ('K_PATH_IMAGES', 'C:/xampp/htdocs/projects/ibill_v3/tcpdf/fonts');
/**
* Deafult image logo used be the default Header() method.
* Please set here your own logo or an empty string to disable it.
*/
//define ('PDF_HEADER_LOGO', '');
/**
* Header logo image width in user units.
*/
//define ('PDF_HEADER_LOGO_WIDTH', 0);
/**
* Cache directory for temporary files (full path).
*/
//define ('K_PATH_CACHE', '/tmp/');
/**
* Generic name for a blank image.
*/
define ('K_BLANK_IMAGE', '_blank.png');
/**
* Page format.
*/
define ('PDF_PAGE_FORMAT', 'A4');
/**
* Page orientation (P=portrait, L=landscape).
*/
define ('PDF_PAGE_ORIENTATION', 'P');
/**
* Document creator.
*/
define ('PDF_CREATOR', 'TCPDF');
/**
* Document author.
*/
define ('PDF_AUTHOR', 'TCPDF');
/**
* Header title.
*/
define ('PDF_HEADER_TITLE', 'TCPDF Example');
/**
* Header description string.
*/
define ('PDF_HEADER_STRING', "by Nicola Asuni - Tecnick.com\nwww.tcpdf.org");
/**
* Document unit of measure [pt=point, mm=millimeter, cm=centimeter, in=inch].
*/
define ('PDF_UNIT', 'mm');
/**
* Header margin.
*/
define ('PDF_MARGIN_HEADER', 5);
/**
* Footer margin.
*/
define ('PDF_MARGIN_FOOTER', 10);
/**
* Top margin.
*/
define ('PDF_MARGIN_TOP', 27);
/**
* Bottom margin.
*/
define ('PDF_MARGIN_BOTTOM', 25);
/**
* Left margin.
*/
define ('PDF_MARGIN_LEFT', 15);
/**
* Right margin.
*/
define ('PDF_MARGIN_RIGHT', 15);
/**
* Default main font name.
*/
define ('PDF_FONT_NAME_MAIN', 'helvetica');
/**
* Default main font size.
*/
define ('PDF_FONT_SIZE_MAIN', 10);
/**
* Default data font name.
*/
define ('PDF_FONT_NAME_DATA', 'helvetica');
/**
* Default data font size.
*/
define ('PDF_FONT_SIZE_DATA', 8);
/**
* Default monospaced font name.
*/
define ('PDF_FONT_MONOSPACED', 'courier');
/**
* Ratio used to adjust the conversion of pixels to user units.
*/
define ('PDF_IMAGE_SCALE_RATIO', 1.25);
/**
* Magnification factor for titles.
*/
define('HEAD_MAGNIFICATION', 1.1);
/**
* Height of cell respect font height.
*/
define('K_CELL_HEIGHT_RATIO', 1.25);
/**
* Title magnification respect main font size.
*/
define('K_TITLE_MAGNIFICATION', 1.3);
/**
* Reduction factor for small font.
*/
define('K_SMALL_RATIO', 2/3);
/**
* Set to true to enable the special procedure used to avoid the overlappind of symbols on Thai language.
*/
define('K_THAI_TOPCHARS', true);
/**
* If true allows to call TCPDF methods using HTML syntax
* IMPORTANT: For security reason, disable this feature if you are printing user HTML content.
*/
define('K_TCPDF_CALLS_IN_HTML', false);
/**
* If true and PHP version is greater than 5, then the Error() method throw new exception instead of terminating the execution.
*/
define('K_TCPDF_THROW_EXCEPTION_ERROR', false);
/**
* Default timezone for datetime functions
*/
define('K_TIMEZONE', 'UTC');
//============================================================+
// END OF FILE
//============================================================+
email.php
<?php
require_once('tcpdf/config/lang/eng.php');
require_once('tcpdf/tcpdf.php');
class ashPDF extends TCPDF {
public function Header() {
$this->setJPEGQuality(90);
$this->Image('logo.png', 62, 36, 72, 0, 'PNG', '/projects/ibill_v3/img/ibill logo.png');
}
public function Footer() {
$this->SetY(-15);
$this->SetFont(PDF_FONT_NAME_MAIN, 'I', 8);
$this->Cell(0, 10, 'php.refulz.com – Web Developer’s Blog', 0, false, 'C');
}
}
$pdf = new ashPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
// $pdf->SetCreator(PDF_CREATOR);
$pdf->SetAuthor('ash PHP');
$pdf->SetTitle('Fisrt PDF Document using TCPDF');
$pdf->SetSubject('ash PDF Example');
$pdf->SetKeywords('ash, TCPDF, PDF');
// add a page
$pdf->AddPage();
$col = 72; // Column size
$wideCol = 3 * $col; // Description Column
$line = 18; // Line height
// Table header
$pdf->SetFont( '', 'b' );
$pdf->Cell( $col, $line, 'S. No.', 1, 0, 'L' );
$pdf->Cell( $wideCol, $line, 'Task', 1, 0, 'L' );
$pdf->Cell( $col, $line, 'Time', 1, 0, 'R' );
$pdf->Ln(); // Adds Line break
// Table content beings here
$pdf->SetFont( '', '' ); // two parameters accept font-family and style. Passing blank sets default values
$counter = 1; // Setting counter for S. No. column
foreach( $this->taskData['items'] as $item ) {
$pdf->Cell( $col, $line, $counter, 1, 0, 'L' );
$pdf->Cell( $wideCol, $line, $item[0], 1, 0, 'L' );
$pdf->Cell( $col, $line, $item[1], 1, 0, 'R' );
$pdf->Ln();
$counter++;
}
// Output the PDF document.
$pdf->Output( 'Tasksheet.pdf', 'D' );
?>
Error Messages when trying to execute email.php:
Warning: require_once(tcpdf/config/lang/eng.php): failed to open stream: No such file or directory in C:\xampp\htdocs\projects\ibill_v3\html\email.php on line 2
Fatal error: require_once(): Failed opening required 'tcpdf/config/lang/eng.php' (include_path='C:\xampp\php\PEAR') in C:\xampp\htdocs\projects\ibill_v3\html\email.php on line 2
I'm having an issue with imagick running on a MAMP installation within a cakephp app.
I have followed the install instructions as indicated here and the install seems to have worked in the sense that the class 'Imagick' exists for me if i test it in a php script (I can see the module is loaded in phpinfo). However any examples that I run utilising the class hang as soon as I echo any content. My view is:
<?php
/* Create a new imagick object */
$im = new Imagick();
/* Create new image. This will be used as fill pattern */
$im->newPseudoImage(50, 50, "gradient:red-black");
/* Create imagickdraw object */
$draw = new ImagickDraw();
/* Start a new pattern called "gradient" */
$draw->pushPattern('gradient', 0, 0, 50, 50);
/* Composite the gradient on the pattern */
$draw->composite(Imagick::COMPOSITE_OVER, 0, 0, 50, 50, $im);
/* Close the pattern */
$draw->popPattern();
/* Use the pattern called "gradient" as the fill */
$draw->setFillPatternURL('#gradient');
/* Set font size to 52 */
$draw->setFontSize(52);
/* Annotate some text */
$draw->annotation(20, 50, "Hello World!");
/* Create a new canvas object and a white image */
$canvas = new Imagick();
$canvas->newImage(350, 70, "white");
/* Draw the ImagickDraw on to the canvas */
$canvas->drawImage($draw);
/* 1px black border around the image */
$canvas->borderImage('black', 1, 1);
/* Set the format to PNG */
$canvas->setImageFormat('png');
/* Output the image */
header("Content-Type: image/png");
echo $canvas;
?>
This script will just hang as soon as the echo $canvas is encountered. The script works perfectly in a plain old php file i.e outside of cake but it hangs when visited via my cakephp app action. My action code is:
public function test(){
$this->layout = false;
}
Cake error log is empty.
Okay the issue was with how the header was set in cakephp. Cake didn't allow me to set the page header in the view that way.
I added the following code to the test action:
$this->response->type("image/png");
And it works perfectly now.
I'm looking to center crop and image using Imagick PHP apis (not command line version of Imagick).
Essentially I want to do what is possible via command line, using API. Here is an example via command line:
http://www.imagemagick.org/Usage/crop/#crop_gravity
Here is what I'm doing (not working). It always crops the upper left corner of the source:
$this->imagickObj->setGravity(\Imagick::GRAVITY_CENTER);
$this->imagickObj->cropImage(300,250,0,0);
$this->imagickObj->setImagePage(0, 0, 0, 0);
Why is the setGravity not applying to the image before the crop? http://www.php.net/manual/en/function.imagick-setgravity.php says it should apply to the object (in this case the single image)...
Its too late for the original person who asked the question but for future visitors, correct solution is
bool Imagick::cropThumbnailImage ( int $width , int $height )
Sorry for late reply but I too stuck here just 30 mins ago and first google result redirected me here. Hope same will not happen with others.
Looks like there is not support, here is how I ended up doing it:
https://gist.github.com/1364489
The Imagemagick object's cropImage() method's 3rd and 4th argument are defining the upper-left corner of the crop. Either try passing those as null (and use the setGravity() method), or you may actually have to calculate where the crop is supposed to take place and pop those numbers into the cropImage() method (and don't bother with setGravity()).
For what it's worth, I have done a lot of coding around Imagemagick using PHP, and due to the horrible documentation of the Imagemagick extension, I resorted to making lots of nice'd command line calls.
I have created component to crop and resize images
here is the code (yii2)
Component uses imagine/imagine extension, install it before
<?php
namespace common\components;
use Imagine\Gd\Imagine;
use Imagine\Image\Box;
use Imagine\Image\ImageInterface;
use Imagine\Image\Point;
use Imagine\Imagick\Image;
class ResizeComponent
{
/**
* Resize image
* #param string $source source image path
* #param string $destination destination image path
* #param int $width
* #param int $height
* #param int $quality Jpeg sampling quality (0-100, 80 is best for seo)
* #return boolean is picture cropped
*/
public static function resizeImage($source, $destination, $width, $height, $quality = 80)
{
if (file_exists($source) && is_file($source)) {
$imagine = new Imagine();
$size = new Box($width, $height);
$mode = ImageInterface::THUMBNAIL_INSET;
$resizeimg = $imagine->open($source)->thumbnail($size, $mode);
$sizeR = $resizeimg->getSize();
$widthR = $sizeR->getWidth();
$heightR = $sizeR->getHeight();
$preserve = $imagine->create($size);
$startX = $startY = 0;
if ($widthR < $width) {
$startX = ($width - $widthR) / 2;
}
if ($heightR < $height) {
$startY = ($height - $heightR) / 2;
}
$preserve->paste($resizeimg, new Point($startX, $startY))
->save($destination, array('jpeg_quality' => $quality));
return true;
} else {
return false;
}
}
/**
* Crop image
* #param string $source source image path
* #param string $destination destination image path
* #param int $width
* #param int $height
* #param int $quality Jpeg sampling quality (0-100, 80 is best for seo)
* #return boolean is picture cropped
*/
public static function cropImage($source, $destination, $width, $height, $quality = 80)
{
if (file_exists($source) && is_file($source)) {
$imagine = new Imagine();
$size = new Box($width, $height);
$mode = ImageInterface::THUMBNAIL_OUTBOUND;
$image = $imagine->open($source)->thumbnail($size, $mode);
$image->thumbnail($size, $mode)->save($destination, array('jpeg_quality' => $quality));
return true;
} else {
return false;
}
}
}
The difference between crop and resize is :
crop cant display all image, so borders will be cropped (best for not informative thumbnails)
resize displays full image, but borders will be filled with static color (or transperency if needed) (best if all image needed to be shown, as in shop catalog)
Use this component statically, best practice as ServiceLocator
Even after a very high score of Google PageSpeed(97) & Yahoo! YSlow(92) the PHP generated thumbnails don't seem to be coming passively from an old cache: they seem to be generated every time again...and again... freshly baked consuming lots of waisted time.
This question will focus only & specifically on how to solve the CACHE problem of the PHP Code that generates the thumbs:
Just have a look at these tiny puny little thumbnails measuring only 3 ~ 5 kb each!
Waterfall in detail: http://www.webpagetest.org/result/110328_AM_8T00/1/details/
Any & all suggestons are +1 help to me and warmly welcome, for I have grown quite desperate on this issue for the last months. Thanx a Thousand!
Using or not Modrewrite does not influence speed: both are same. I use these rewrite conditions: RewriteCond %{REQUEST_URI} ^/IMG-.*$ & RewriteCond %{REQUEST_FILENAME} !-f
Both the original default URL as well as the beautified rewritten URL produce the same delays!! So let us not point the fault to the lightning fast Apache: its the PHP Cache / headers that are somehow wrongly coded...
Warning by webpagetest.org: Leverage browser caching of static assets: 69/100
FAILED - (No max-age or expires): http://aster.nu/imgcpu?src=aster_bg/124.jpg&w=1400&h=100&c=p
After each refresh, you will see either of these two warnings appear on random at REDbot.org
Relevant Portions of The Code:
// Script is directly called
if(isset($_GET['src']) && (isset($_GET['w']) || isset($_GET['h']) || isset($_GET['m']) || isset($_GET['f']) || isset($_GET['q']))){
$ImageProcessor = new ImageProcessor(true);
$ImageProcessor->Load($_GET['src'], true);
$ImageProcessor->EnableCache("/var/www/vhosts/blabla.org/httpdocs/tmp/", 345600);
$ImageProcessor->Parse($quality);
}
/* Images processing class
* - create image thumbnails on the fly
* - Can be used with direct url imgcpu.php?src=
* - Cache images for efficiency
*/
class ImageProcessor
{
private $_image_path; # Origninal image path
protected $_image_name; # Image name string
private $_image_type; # Image type int
protected $_mime; # Image mime type string
private $_direct_call = false; # Is it a direct url call? boolean
private $_image_resource; # Image resource var Resource
private $_cache_folder; # Cache folder strig
private $_cache_ttl; # Cache time to live int
private $_cache = false; # Cache on boolean
private $_cache_skip = false; # Cache skip var boolean
private function cleanUrl($image){ # Cleanup url
$cimage = str_replace("\\", "/", $image);
return $cimage;
}
/** Get image resource
* #access private, #param string $image, #param string $extension, #return resource */
private function GetImageResource($image, $extension){
switch($extension){
case "jpg":
#ini_set('gd.jpeg_ignore_warning', 1);
$resource = imagecreatefromjpeg($image);
break;
}
return $resource;
}
/* Save image to cache folder
* #access private, #return void */
private function cacheImage($name, $content){
# Write content file
$path = $this->_cache_folder . $name;
$fh = fopen($path, 'w') or die("can't open file");
fwrite($fh, $content);
fclose($fh);
# Delete expired images
foreach (glob($this->_cache_folder . "*") as $filename) {
if(filemtime($filename) < (time() - $this->_cache_ttl)){
unlink( $filename );
}
}
}
/* Get an image from cache
* #access public, #param string $name, #return void */
private function cachedImage($name){
$file = $this->_cache_folder . $name;
$fh = fopen($file, 'r');
$content = fread($fh, filesize($file));
fclose($fh);
return $content;
}
/* Get name of the cache file
* #access private, #return string */
private function generateCacheName(){
$get = implode("-", $_GET);
return md5($this->_resize_mode . $this->_image_path . $this->_old_width . $this->_old_height . $this->_new_width . $this->_new_height . $get) . "." . $this->_extension;
}
/* Check if a cache file is expired
* #access private, #return bool */
private function cacheExpired(){
$path = $this->_cache_folder . $this->generateCacheName();
if(file_exists($path)){
$filetime = filemtime($path);
return $filetime < (time() - $this->_cache_ttl);
}else{
return true;
}
}
/* Lazy load the image resource needed for the caching to work
* #return void */
private function lazyLoad(){
if(empty($this->_image_resource)){
if($this->_cache && !$this->cacheExpired()){
$this->_cache_skip = true;
return;
}
$resource = $this->GetImageResource($this->_image_path, $this->_extension);
$this->_image_resource = $resource;
}
}
/* Constructor
* #access public, #param bool $direct_call, #return void */
public function __construct($direct_call=false){
# Check if GD extension is loaded
if (!extension_loaded('gd') && !extension_loaded('gd2')) {
$this->showError("GD is not loaded");
}
$this->_direct_call = $direct_call;
}
/* Resize
* #param int $width, #param int $height, #param define $mode
* #param bool $auto_orientation houd rekening met orientatie wanneer er een resize gebeurt */
public function Resize($width=100, $height=100, $mode=RESIZE_STRETCH, $auto_orientation=false){
// Validate resize mode
$valid_modes = array("f", "p");
}
// .... omitted .....
// Set news size vars because these are used for the
// cache name generation
// .... omitted .....
$this->_old_width = $width;
$this->_old_height = $height;
// Lazy load for the directurl cache to work
$this->lazyLoad();
if($this->_cache_skip) return true;
// Create canvas for the new image
$new_image = imagecreatetruecolor($width, $height);
imagecopyresampled($new_image, $this->_image_resource, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h);
// .... omitted .....
$this->_image_resource = $new_image;
}
/* Create image resource from path or url
* #access public, #param string $location, #param bool $lazy_load, #return */
public function Load($image,$lazy_load=false){
// Cleanup image url
$image = $this->cleanUrl($image);
// Check if it is a valid image
if(isset($mimes[$extension]) && ((!strstr($image, "http://") && file_exists($image)) || strstr($image, "http://")) ){
// Urlencode if http
if(strstr($image, "http://")){
$image = str_replace(array('http%3A%2F%2F', '%2F'), array('http://', '/'), urlencode($image));
}
$image = str_replace("+", "%20", $image);
$this->_extension = $extension;
$this->_mime = $mimes[$extension];
$this->_image_path = $image;
$parts = explode("/", $image);
$this->_image_name = str_replace("." . $this->_extension, "", end($parts));
// Get image size
list($width, $height, $type) = getimagesize($image);
$this->_old_width = $width;
$this->_old_height = $height;
$this->_image_type = $type;
}else{
$this->showError("Wrong image type or file does not exists.");
}
if(!$lazy_load){
$resource = $this->GetImageResource($image, $extension);
$this->_image_resource = $resource;
}
}
/* Save image to computer
* #access public, #param string $destination, #return void */
public function Save($destination, $quality=60){
if($this->_extension == "png" || $this->_extension == "gif"){
imagesavealpha($this->_image_resource, true);
}
switch ($this->_extension) {
case "jpg": imagejpeg($this->_image_resource,$destination, $quality); break;
case "gif": imagegif($this->_image_resource,$destination); break;
default: $this->showError('Failed to save image!'); break;
}
}
/* Print image to screen
* #access public, #return void */
public function Parse($quality=60){
$name = $this->generateCacheName();
$content = "";
if(!$this->_cache || ($this->_cache && $this->cacheExpired())){
ob_start();
header ("Content-type: " . $this->_mime);
if($this->_extension == "png" || $this->_extension == "gif"){
imagesavealpha($this->_image_resource, true);
}
switch ($this->_extension) {
case "jpg": imagejpeg($this->_image_resource, "", $quality); break;
case "gif": imagegif($this->_image_resource); break;
default: $this->showError('Failed to save image!'); break;
}
$content = ob_get_contents();
ob_end_clean();
}else{
if (isset ($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
if (strtotime ($_SERVER['HTTP_IF_MODIFIED_SINCE']) < strtotime('now')) {
header ('HTTP/1.1 304 Not Modified');
die ();
}
}
// change the modified headers
$gmdate_expires = gmdate ('D, d M Y H:i:s', strtotime ('now +10 days')) . ' GMT';
$gmdate_modified = gmdate ('D, d M Y H:i:s') . ' GMT';
header ("Content-type: " . $this->_mime);
header ('Accept-Ranges: bytes');
header ('Last-Modified: ' . $gmdate_modified);
header ('Cache-Control: max-age=864000, must-revalidate');
header ('Expires: ' . $gmdate_expires);
echo $this->cachedImage($name);
exit();
}
// Save image content
if(!empty($content) && $this->_cache){
$this->cacheImage($name, $content);
}
// Destroy image
$this->Destroy();
echo $content;
exit();
}
/* Destroy resources
* #access public, #return void */
public function Destroy(){
imagedestroy($this->_image_resource);
}
/* Get image resources
* #access public, #return resource */
public function GetResource(){
return $this->_image_resource;
}
/* Set image resources
* #access public, #param resource $image, #return resource */
public function SetResource($image){
$this->_image_resource = $image;
}
/* Enable caching
* #access public, #param string $folder, #param int $ttl, * #return void */
public function EnableCache($folder="/var/www/vhosts/blabla.org/httpdocs/tmp/", $ttl=345600){
if(!is_dir($folder)){
$this->showError("Directory '" . $folder . "' does'nt exist");
}else{
$this->_cache = true;
$this->_cache_folder = $folder;
$this->_cache_ttl = $ttl;
}
return false;
}
}
The original author granted me permission for placing parts of code in here for solving this issue.
If I'm understanding the question correctly, this is entirely to be expected. Image manipulation is slow.
The yellow is your browser sending the request. The green is your browser waiting on the server to actually create the thumbnail, which takes a very significant amount of time, no matter what library the server is using. The blue is the server sending the response, which, unlike the previous steps, is affected by filesize.
There's not much to be done about the inherent slowness of image manipulation. It would be wise to cache these thumbnails so that they are only generated once and are then served statically. That way, very few of your users will ever have to sit through that green delay, and your server will be happy, too.
EDIT: If the issue is that the files exist at those URLs, but your RewriteRule is kicking in anyway, bear in mind that, by default, rules run without checking if the file exists.
Use the following condition above your RewriteRule to make sure the file exists.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule # ...etc...
imgcpu.php?src=foo/foo.jpg&w=100&h=100
so imgcpu.php is running for every image request?
In that case, if you are worried about performance,:
the script needs to do some caching of the thumbnails it creates. If it resizes stuff on every request, that's your problem right there.
the script needs to send some caching headers to the browser - a pure PHP script won't do that, and will be refreshed on every page load
a session_start() call inside the PHP script could lead to concurrency issues because of session locking.
you will need to show some PHP code. Maybe in a separate question, though.
Apache can serve up files from your hard disk a lot faster than PHP can, and it appears that you're doing the latter to handle caching:
/**
* Get an image from cache
*
* #access public
* #param string $name
* #return void
*/
private function cachedImage($name){
$file = $this->_cache_folder . $name;
$fh = fopen($file, 'r');
$content = fread($fh, filesize($file));
fclose($fh);
return $content;
}
There's a better way of doing what that function is doing (passthru), but the best option is to setup a regex that'll only rewrite a request to your thumbnailing script if the file doesn't already exist:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^images/.*$ - [NC,L]
RewriteRule ^images/(.*)$ /imgcpu.php/$1 [NC,L]
And then introduce logic to parse the request to an image and format it accordingly. For example, you could say thumbs should be named after the original file and have the W x H dimensions appended like "stackoverflow_logo_100x100.jpg".
Make sense?
Per request (in the comment), the description of the "s", "l", and "d" flags is as follows (quoting the docs):
'-d' (is directory) Treats the
TestString as a pathname and tests
whether or not it exists, and is a
directory.
'-s' (is
regular file, with size) Treats the
TestString as a pathname and tests
whether or not it exists, and is a
regular file with size greater than
zero.
'-l' (is symbolic link) Treats
the TestString as a pathname and tests
whether or not it exists, and is a
symbolic link.
Your checking your HTTP_IF_MODIFIED_SINCE header and cache AFTER you generate the image so the image is getting generated and cached every time you load the page. You would get a considerable decrease in time if you move these checks closer to the start of execution, before you start processing the image.
Matchu gave you answer why. If you want to fix it, save the created thumbnails, so they are not recreated on each request. I use simple 404 page that catches request to thumbnails that haven't been created, that script figures out the required dimensions and file from url - eg /thumbs/100x100/cat.png means create 100x100 thumbnail from /images/cat.png.
I am currently trying to add a progress bar to a command line script and I've tried various solutions (including Zend and Console_ProgressBar). The problem they both have in common is that the progress bar doesn't stick at the bottom of the window because during the script, new lines and other information is outputted.
Is there any way to keep the progress bar at the bottom of the terminal but still be able to output other information while the script is running?
[Edit]
I figured it out:
Instead of outputting directly to STDOUT I am actually grabbing the output inside a variable, I erase the screen with echo chr(27) . '[2J' and then output to STDOUT the contents of the variable and then append my progress bar.
Hope that makes sense :)
This is nice progress bar for cli:
http://snipplr.com/view/29548/
<?php
/*
Copyright (c) 2010, dealnews.com, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of dealnews.com, Inc. nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
/**
* show a status bar in the console
*
* <code>
* for($x=1;$x<=100;$x++){
*
* show_status($x, 100);
*
* usleep(100000);
*
* }
* </code>
*
* #param int $done how many items are completed
* #param int $total how many items are to be done total
* #param int $size optional size of the status bar
* #return void
*
*/
function show_status($done, $total, $size=30) {
static $start_time;
// if we go over our bound, just ignore it
if($done > $total) return;
if(empty($start_time)) $start_time=time();
$now = time();
$perc=(double)($done/$total);
$bar=floor($perc*$size);
$status_bar="\r[";
$status_bar.=str_repeat("=", $bar);
if($bar<$size){
$status_bar.=">";
$status_bar.=str_repeat(" ", $size-$bar);
} else {
$status_bar.="=";
}
$disp=number_format($perc*100, 0);
$status_bar.="] $disp% $done/$total";
$rate = ($now-$start_time)/$done;
$left = $total - $done;
$eta = round($rate * $left, 2);
$elapsed = $now - $start_time;
$status_bar.= " remaining: ".number_format($eta)." sec. elapsed: ".number_format($elapsed)." sec.";
echo "$status_bar ";
flush();
// when done, send a newline
if($done == $total) {
echo PHP_EOL;
}
}
?>
Other answers seem overly complex. My solution is to simply echo \033[0G escape sequence before the next update and it moves the cursor back to the beginning.
function progressBar($done, $total) {
$perc = floor(($done / $total) * 100);
$left = 100 - $perc;
$write = sprintf("\033[0G\033[2K[%'={$perc}s>%-{$left}s] - $perc%% - $done/$total", "", "");
fwrite(STDERR, $write);
}
Calling the function for the first time outputs the progress bar and each subsequent call overwrites the last line with a new progress bar.
EDIT:
I have changed echoing \r to the escape sequence \033[0G and this should now work on OSX as well as Linux/Unix.
EDIT 2:
Fixed possible error on line 3 as per #tbjers suggestion.
EDIT 3:
Updated with new version that prints to STDERR and now also on GitHub: https://github.com/MacroMan/PHPTerminalProgressBar
EDIT 4:
Now with composer: composer require macroman/terminal-progress-bar
use TerminalProgress\Bar;
$pg = new Bar(1000);
for ($i = 0; $i < 1000; $i++) {
usleep(10000);
$pg->tick();
}
The following is for unix machines.
The goal is to retrieve the current terminal total columns. (using tput)
This is a base, ready to be expanded.
#!/usr/bin/php
<?php
#ob_start();
$shell = system("tput cols");
#ob_end_clean();
for( $i= 0 ; $i < $shell ; $i++ ){ echo "█"; usleep(100000); }
The ob actions are here to hide the stdout of tput.
Let's say you would like to make a progress bar matching your task progress.
Just divide the remaining time in seconds by the numbers of columns.
You may end up with microseconds, so it's best to use usleep.
This way the progress bar will always match the user shell width, including when resized.
To do the same thing in pure bash:
for ((i=0; i<$(tput cols); i++)); do echo -e "█\c"; sleep 0.1; done
This shows the main singularity of the php echo: it does not append a new line, while the bash echo does.
When using loops, say to check for a condition in intervals,
one other nice way to warn for running activity is text effects.
The following using strtoupper and the ansi code reverse video.
#!/usr/bin/php
<?php
$iloop = "0"; /* Outside the loop */
while (true){
$warn = "Program running hold on!!\r";
if (strlen($warn) === $iloop+1){
$iloop = "0";
}
$warn = str_split($warn);
$iloop++;
$warn[$iloop] = "\033[35;2m\e[0m".strtoupper($warn[$iloop]);
echo " \033[7m".implode($warn);
usleep(90000);
}
Output:
Some may likes the party version, obtained by iteration of ansi codes.
#!/usr/bin/php
<?php
$iloop = "0"; /* Outside the loop */
while (true){
for ($i=0;$i<=109;$i++){
$warn = "Program running hold on!!\r";
if (strlen($warn) === $iloop+1){
$iloop = "0";
}
$warn = str_split($warn);
$iloop++;
$warn[$iloop] = "\033[$i;7m".strtoupper($warn[$iloop]);
echo " \033[7m".implode($warn);
usleep(90000);
}}
See more about ANSI codes: https://stackoverflow.com/a/48365998/2494754
This is an improvement of the previous answer which handles terminal resizes and uses 2 lines instead of 1. First line is info like time/percent second line is the progress bar.
<?
/*
Copyright (c) 2010, dealnews.com, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of dealnews.com, Inc. nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
/**
* show a status bar in the console
*
* <code>
* for($x=1;$x<=100;$x++){
*
* show_status($x, 100);
*
* usleep(100000);
*
* }
* </code>
*
* #param int $done how many items are completed
* #param int $total how many items are to be done total
* #param int $size optional size of the status bar
* #return void
*
*/
function show_status($done, $total, $size=30, $lineWidth=-1) {
if($lineWidth <= 0){
$lineWidth = $_ENV['COLUMNS'];
}
static $start_time;
// to take account for [ and ]
$size -= 3;
// if we go over our bound, just ignore it
if($done > $total) return;
if(empty($start_time)) $start_time=time();
$now = time();
$perc=(double)($done/$total);
$bar=floor($perc*$size);
// jump to the begining
echo "\r";
// jump a line up
echo "\x1b[A";
$status_bar="[";
$status_bar.=str_repeat("=", $bar);
if($bar<$size){
$status_bar.=">";
$status_bar.=str_repeat(" ", $size-$bar);
} else {
$status_bar.="=";
}
$disp=number_format($perc*100, 0);
$status_bar.="]";
$details = "$disp% $done/$total";
$rate = ($now-$start_time)/$done;
$left = $total - $done;
$eta = round($rate * $left, 2);
$elapsed = $now - $start_time;
$details .= " " . formatTime($eta)." ". formatTime($elapsed);
$lineWidth--;
if(strlen($details) >= $lineWidth){
$details = substr($details, 0, $lineWidth-1);
}
echo "$details\n$status_bar";
flush();
// when done, send a newline
if($done == $total) {
echo "\n";
}
}
I don't know why the above code has a license I'm just copying it to be safe. The bellow code is under no license. Free to use for any purpose.
function formatTime($sec){
if($sec > 100){
$sec /= 60;
if($sec > 100){
$sec /= 60;
return number_format($sec) . " hr";
}
return number_format($sec) . " min";
}
return number_format($sec) . " sec";
}
class Timer {
public $time;
function __construct(){
$this->start();
}
function start($offset=0){
$this->time = microtime(true) + $offset;
}
function seconds(){
return microtime(true) - $this->time;
}
};
// We need this to limit the frequency of the progress bar. Or else it
// hugely slows down the app.
class FPSLimit {
public $frequency;
public $maxDt;
public $timer;
function __construct($freq){
$this->setFrequency($freq);
$this->timer = new Timer();
$this->timer->start();
}
function setFrequency($freq){
$this->frequency = $freq;
$this->maxDt = 1.0/$freq;
}
function frame(){
$dt = $this->timer->seconds();
if($dt > $this->maxDt){
$this->timer->start($dt - $this->maxDt);
return true;
}
return false;
}
};
class Progress {
// generic progress class to update different things
function update($units, $total){}
}
class SimpleProgress extends Progress {
private $cols;
private $limiter;
private $units;
private $total;
function __construct(){
// change the fps limit as needed
$this->limiter = new FPSLimit(10);
echo "\n";
}
function __destruct(){
$this->draw();
}
function updateSize(){
// get the number of columns
$this->cols = exec("tput cols");
}
function draw(){
$this->updateSize();
show_status($this->units, $this->total, $this->cols, $this->cols);
}
function update($units, $total){
$this->units = $units;
$this->total = $total;
if(!$this->limiter->frame())
return;
$this->draw();
}
}
// example
$tasks = rand() % 700 + 600;
$done = 0;
$progress = new SimpleProgress();
for($done = 0; $done <= $tasks; $done++){
usleep((rand() % 127)*100);
$progress->update($done, $tasks);
}
If you want to use something that can be used in different terminals without having to worry about compatibility, Ncurses is the way to go.
Check: https://www.whoishostingthis.com/resources/ncurses/
The ncurses library provides a robust framework which allows programmers to create visually appealing user interfaces in text mode
Main PHP documentation point about this is: https://www.php.net/manual/en/book.ncurses.php
echo "Progress";
$progress = false;
while ($progress==false){
echo ".";
sleep(1);
}
#enter code here
$progress == true;
if($progress == true){
# Do something only when done
# Code here
echo " Done!";
}