PHP Text to Image Coversion
Views:
You've been able to get browsers to download fonts for a while now but you still need a license. You need a license for most font's if you use this too, although you're more likely to get one. This is also great for that other web browser.
You need to install the MagickWand PHP extension for this to work. Good luck. You're mad if you don't port this to imagick.
//Example:
try
{
$TTI = new NinjaTextToImage();
$TTI->setBGImage('/var/www/example.com/blank-2000x50.png');
$TTI->setFontSize(15);
$TTI->setFontColour('#666666');
$TTI->setUnderAlpha(1);
$TTI->enableAA(true);
$TTI->setFont('/var/www/example.com/fonts/FuturaStd-Medium.ttf');
$TTI->setFormat('PNG');
$TTI->enableTrim(true);
$TTI->setText('Biscuits');
$TTI->convert();
//header()....
echo $TTI->getBlob();
}
catch (Exception $e)
{
error_log(__METHOD__." caught exception trying to convert text to image: $e");
return null;
}
<?php
/**
* A class to keep designers happy.
* (A class to convert text to an image of that text.)
*
* A note on colours: Image Magick colour strings are very flexible, check the
* ImageMagick web site for full details. Here are a couple of examples to give
* you a flavour -
*
* $TTI->setFontColour('rgba(100,67,132,0.1)'); // RGB colours and alpha channel
* $TTI->setFontColour('#4f2682');
* $TTI->setFontColour('blue');
*
* ... and an incomplete list...
* HEX #00ffff
* RGB rgb(0,255,255)
* RGBA rgba(0,255,255,1)
* HSL hsl(180,100,50)
* HSLA hsla(180,100,50,1.0)
* CMYK cmyk(100,0,0,0)
* CMYKA cmyka(100,0,0,0,100)
*
* Built to work with MagickWand, however it should be easy enough to
* create an API compatible GD version
*
* Known issues:
* you might have problems enabling Trim when you are using a fancy font that
* draws out side it's boundaries.
* Text is left, top aligned, no option to change that at the moment
* Probably won't cope very well with many many lines or very long lines
* of text unless you setBGImage to use a large enough file
*
* @version 0.1
* @package ninja
* @author Kieran Whitbread
* @copyright Kieran Whitbread 2006-2010
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
*
**/
class NinjaTextToImage
{
protected $antiAliased = true;
protected $bgAlpha = 1;
protected $bgColour = null;
protected $bgPixelWand = null;
protected $bgImageFile = null;
protected $drawingWand = null;
protected $fgAlpha = 1;
protected $fgColor = 'black';
protected $fgPixelWand = null;
protected $fontFile = null;
protected $fontSize = null;
protected $format = null;
protected $lineHeight = null;
protected $magickWand = null;
protected $text = null;
protected $trim = true;
protected $underAlpha = 1;
protected $underColour = null;
protected $underPixelWand = null;
protected $x = 200;
protected $y = 50;
public function __construct()
{
$this->magickWand = NewMagickWand();
$this->drawingWand = NewDrawingWand();
DrawSetGravity($this->drawingWand, MW_NorthWestGravity);
$this->bgPixelWand = NewPixelWand();
$this->fgPixelWand = NewPixelWand();
$this->setFontAlpha($this->fgAlpha);
$this->underPixelWand = NewPixelWand();
$this->setUnderAlpha($this->underAlpha);
MagickSetFormat($this->magickWand, 'PNG');
MagickSetImageCompression($this->magickWand, MW_ZIPCompression);
}
public function __destruct()
{
if (IsMagickWand($this->magickWand)) DestroyMagickWand($this->magickWand);
if (IsDrawingWand($this->drawingWand)) DestroyDrawingWand($this->drawingWand);
if (IsPixelWand($this->bgPixelWand)) DestroyPixelWand($this->bgPixelWand);
if (IsPixelWand($this->fgPixelWand)) DestroyPixelWand($this->fgPixelWand);
if (IsPixelWand($this->underPixelWand)) DestroyPixelWand($this->underPixelWand);
}
/**
* Run the text to image conversion process, note that object must be put in a suitable state
* before calling this method. Known issue: Fonts who's glyphs draw out side their specified
* boundaries (e.g. Loki Cola) will get clipped if enableTrim has been enabled.
*
* @return bool True on success or false on failure
* @author Kieran Whitbread
**/
public function convert()
{
if ($this->trim)
{
/*
* This isn't very neat, but the best way I can figure out for the first go
*
* We need to make sure we get a consistent line height for a given
* font and font size and this is a tricky thing to do. The MagickTrimImage
* & DrawSetUnderColor functions come to the rescue. They basically work by
* trimming off the background. In order to do that we need to use
* some predetermined high constrast colours.
*/
// Tempory Pixel Wands to hold our colours
$tempFgPixelWand = NewPixelWand();
$tempUnderPixelWand = NewPixelWand();
$tempBgPixelWand = NewPixelWand();
// Temporay Magick Wand to use for trimming and calculating our desired x & y pixels
$tempMagickWand = CloneMagickWand($this->magickWand);
// Temporay Drawing wand - we must use the same font and font size in this process
$tempDrawingWand = CloneDrawingWand($this->drawingWand);
// The colours we set will cause a black background to fill in the boundaries for each glyph
PixelSetColor($tempFgPixelWand, "white");
PixelSetColor($tempUnderPixelWand, "black");
//PixelSetColor($tempBgPixelWand, "white");
PixelSetAlpha($tempUnderPixelWand, 1);
DrawSetFillColor($tempDrawingWand, $tempFgPixelWand);
DrawSetTextUnderColor($tempDrawingWand, $tempUnderPixelWand);
DrawAnnotation($tempDrawingWand, 0, 0, $this->text);
MagickDrawImage($tempMagickWand, $tempDrawingWand);
MagickTrimImage($tempMagickWand);
$width = MagickGetImageWidth($tempMagickWand);
$height = MagickGetImageHeight($tempMagickWand);
// Now we know what size the text will be we can resize the main image resource
// and get back to business
MagickResizeImage($this->magickWand, $width, $height, MW_QuadraticFilter, 1.0);
}
// These next two blocks are most likely redundent now (sorry no time to check at the moment)
if($this->underColour)
{
DrawSetTextUnderColor($this->drawingWand, $this->underPixelWand);
if (WandGetExceptionType($this->drawingWand) != MW_UndefinedException)
{
throw new UnexpectedValueException(WandGetExceptionString($this->drawingWand));
}
}
if ($this->bgColour)
{
MagickNewImage($this->magickWand, $this->x, $this->y, PixelGetColorAsString($this->bgPixelWand));
if (WandGetExceptionType($this->magickWand) != MW_UndefinedException)
{
throw new UnexpectedValueException(WandGetExceptionString($this->magickWand));
}
}
DrawAnnotation($this->drawingWand, 0, 0, $this->text);
MagickDrawImage($this->magickWand, $this->drawingWand);
if (WandGetExceptionType($this->magickWand) != MW_UndefinedException)
{
throw new UnexpectedValueException(WandGetExceptionString($this->magickWand));
}
}
/**
* Enable/Disable Anti Aliasing on rendered text by passing true or false as an argument
*
* @return void
* @param bool $on Enable/Disable
* @throws UnexpectedValueException
* @author Kieran Whitbread
**/
public function enableAA($on = true)
{
DrawSetTextAntialias($this->drawingWand, (bool) $on);
if (WandGetExceptionType($this->drawingWand) == MW_UndefinedException)
{
$this->antiAliased = (bool) $on;
}
else
{
throw new UnexpectedValueException(WandGetExceptionString($this->drawingWand));
}
}
/**
* Enable / Disable automatically cropping the image down to the size of the text
*
* @return void
* @param bool $on Enable/Disable automatic trimming of rendered image
* @author Kieran Whitbread
**/
public function enableTrim($on = true)
{
$this->trim = (bool)$on;
}
/**
* Get the contents of the image, in it's current state. Save it in a file or
* echo it out over http.
*
* @return (string) Binary Large Object
* @author Kieran Whitbread
**/
public function getBlob()
{
return MagickGetImageBlob($this->magickWand);
}
/**
* Get lots of information about the chosen font to be used in an image
* Font must already be set, and text must be set or passed as first argument
*
* @param string $text
* @return array|null
* @author Kieran Whitbread
**/
public function getFontMetrics($text = null)
{
if (!$this->fontFile) return null;
$text = !is_null($text) ? $text : $this->text;
if (is_null($text)) return null;
return MagickQueryFontMetrics($this->magickWand, $this->drawingWand, $text, true);
}
/**
* @return (int) Image Height in Pixels
* @author Kieran Whitbread
**/
public function getHeight()
{
return MagickGetImageHeight($this->magickWand);
}
/**
* @return (string) The mime type of the Image Format in use
* @author Kieran Whitbread
**/
public function getMimeType()
{
return MagickGetMimeType($this->magickWand);
}
/**
* @return (int) Image Width in Pixels
* @author Kieran Whitbread
**/
public function getWidth()
{
return MagickGetImageWidth($this->magickWand);
}
/**
* @return (int) The size of the current image in bytes
* @author Kieran Whitbread
**/
public function getSize()
{
return MagickGetImageSize($this->magickWand);
}
/**
* setBGAlpha
*
* @return void
* @author Kieran Whitbread
**/
public function setBGAlpha($level)
{
if ($level < 0 || $level > 1)
{
throw new InvalidArgumentException(' value of color/alpha/opacity level argument ('.$level.') was invalid. Value must be normalized to "0 <= level <= 1"');
}
PixelSetAlpha($this->bgPixelWand, $level);
if (WandGetExceptionType($this->bgPixelWand) == MW_UndefinedException)
{
$this->bgAlpha = $level;
}
else
{
throw new InvalidArgumentException(WandGetExceptionString($this->bgPixelWand));
}
}
/**
* Set the back ground colour to be used for the image
*
* @param string $colour An ImageMagick colour string
* @throws InvalidArgumentException
* @return void
* @author Kieran Whitbread
**/
public function setBGColour($colour)
{
PixelSetColor($this->bgPixelWand, $colour);
if (WandGetExceptionType($this->bgPixelWand) == MW_UndefinedException)
{
$this->bgColour = $colour;
}
else
{
throw new InvalidArgumentException(WandGetExceptionString($this->bgPixelWand));
}
}
/**
* Set the image to use as the background upon which the text will be rendered
*
* @param string $path path to the image file
* @throws InvalidArgumentException|UnexpectedValueException
* @return void
* @author Kieran Whitbread
**/
public function setBGImage($path)
{
if (!is_string($path) || !is_file($path))
{
throw new InvalidArgumentException(' a valid path to an image file is required');
}
MagickReadImage($this->magickWand, $path);
if (WandGetExceptionType($this->magickWand) == MW_UndefinedException)
{
$this->bgImageFile = $path;
}
else
{
throw new UnexpectedValueException(WandGetExceptionString($this->magickWand));
}
}
/**
* Set the Alpha Channel level for the font colour of the text. YMMV appears to have no effect on
* font colour, so until repaired/removed, pass the alpha channel information in using setFontColour
* with an rgba() ImageMagick colour string.
*
* @throws InvalidArgumentException|UnexpectedValueException
* @param float $level The alpha level specified as a decimal fraction, between 0 and 1 inclusive
* @return void
* @author Kieran Whitbread
* @todo This doesn't seem to work, no errors are generated, it's just simply ignored. Investigate if this is
* another part of MagickWand they have written an interface with no implementation.
**/
public function setFontAlpha($level)
{
if ($level < 0 || $level > 1)
{
throw new InvalidArgumentException(' value of color/alpha/opacity level argument ('.$level.') was invalid. Value must be normalized to "0 <= color_val <= 1"');
}
PixelSetAlpha($this->fgPixelWand, $level);
if (WandGetExceptionType($this->fgPixelWand) == MW_UndefinedException)
{
$this->fgAlpha = $level;
}
else
{
throw new UnexpectedValueException(WandGetExceptionString($this->fgPixelWand));
}
}
/**
* Set the font colour for the text, using any ImageMagick colour string
*
* @throws InvalidArgumentException
* @param string $colour An ImageMagick colour string
* @return void
* @author Kieran Whitbread
**/
public function setFontColour($colour)
{
PixelSetColor($this->fgPixelWand, $colour);
if (WandGetExceptionType($this->fgPixelWand) == MW_UndefinedException)
{
$this->fgColour = $colour;
DrawSetFillColor($this->drawingWand, $this->fgPixelWand);
if (WandGetExceptionType($this->drawingWand) != MW_UndefinedException)
{
throw new UnexpectedValueException(WandGetExceptionString($this->drawingWand));
}
}
else
{
throw new InvalidArgumentException(WandGetExceptionString($this->fgPixelWand));
}
}
/**
* Set the font file to be used when rendering the text
*
* @param string $fontFile Path to a True Type Font File (extention: ttf)
* @throws InvalidArgumentException|UnexpectedValueException
* @return void
* @author Kieran Whitbread
**/
public function setFont($fontFile)
{
if (!is_string($fontFile) || strtolower(substr($fontFile, strlen($fontFile) - 3)) != 'ttf' || !is_file($fontFile))
{
throw new InvalidArgumentException(' a valid path to a true type font file is required');
}
DrawSetFont($this->drawingWand, $fontFile);
if (WandGetExceptionType($this->drawingWand) == MW_UndefinedException)
{
$this->fontFile = $fontFile;
}
else
{
throw new UnexpectedValueException(WandGetExceptionString($this->drawingWand));
}
}
/**
* @param int $size Font size for text in pixels
* @throws InvalidArgumentException
* @return void
* @author Kieran Whitbread
**/
public function setFontSize($size)
{
if (!preg_match(NINJA_PREG_UINT, $size) || $size < 1)
{
throw new InvalidArgumentException(' value of the size argument must be a positive integer greater than 0');
}
DrawSetFontSize($this->drawingWand, $size);
if (WandGetExceptionType($this->drawingWand) == MW_UndefinedException)
{
$this->fontSize = $size;
}
else
{
throw new UnexpectedValueException(WandGetExceptionString($this->drawingWand));
}
}
/**
* Set the format of the image
*
* @param string $format Image format, one of JPEG, PNG, GIF
* @param float $jpegCompressionLevel Compression Level for JPEGS, must be an integer between 1 and 100 inclusive
* @throws InvalidArgumentException|UnexpectedValueException
* @return void
* @author Kieran Whitbread
**/
public function setFormat($format, $jpegCompressionLevel = null)
{
$validFormats = array('JPG', 'JPEG', 'PNG', 'GIF');
if (!is_string($format) || !in_array(strtoupper($format), $validFormats))
{
throw new InvalidArgumentException(' value of format argument must a valid image format ('.implode(', ', $validFormats).')');
}
$format = strtoupper($format);
if ($format == 'JPEG')
{
$format = 'JPG';
}
MagickSetFormat($this->magickWand, $format);
if (MagickGetExceptionType($this->magickWand) != MW_UndefinedException)
{
throw new UnexpectedValueException(MagickGetExceptionString($this->magickWand));
}
if ($format == 'JPG')
{
if (!is_float($jpegCompressionLevel) || $jpegCompressionLevel < 0 || $jpegCompressionLevel > 100)
{
throw new InvalidArgumentException('value of jpegCompressionLevel must be an real number between 0 and 100 inclusive');
}
MagickSetImageCompression($this->magickWand, MW_JPEGCompression);
if (WandGetExceptionType($this->magickWand) != MW_UndefinedException)
{
throw new UnexpectedValueException(WandGetExceptionString($this->magickWand));
}
MagickSetImageCompressionQuality($this->magickWand, $jpegCompressionLevel);
if (WandGetExceptionType($this->magickWand) != MW_UndefinedException)
{
throw new UnexpectedValueException(WandGetExceptionString($this->magickWand));
}
}
}
/**
* @param int $height Text line height in pixels (positive int > 0)
* @return void
* @throws InvalidArgumentException
* @author Kieran Whitbread
**/
public function setLineHeight($height)
{
if (!preg_match(NINJA_PREG_UINT, $height) || $height < 1)
{
throw new InvalidArgumentException(' value of the heigh argument must be a positive integer greater than 0');
}
$this->lineHeight = $height;
}
/**
* Set the text that will be converted to an image
*
* @throws InvalidArgumentException
* @param string $text The text to be converted to an image
* @return void
* @author Kieran Whitbread
**/
public function setText($text)
{
if (!is_string($text)) throw new InvalidArgumentException('requires one argument which must be a string');
$this->text = $text;
}
/**
* Set the Alpha Channel level for the under colour of the text
*
* @throws InvalidArgumentException
* @param float $level The alpha level specified as a decimal fraction, between 0 and 1 inclusive
* @return void
* @author Kieran Whitbread
**/
public function setUnderAlpha($level)
{
if ($level < 0 || $level > 1)
{
throw new InvalidArgumentException(' value of color/alpha/opacity argument ('.$level.') was invalid. Value must be normalized to "0 <= color_val <= 1"');
}
PixelSetAlpha($this->underPixelWand, $level);
if (WandGetExceptionType($this->underPixelWand) == MW_UndefinedException)
{
$this->underAlpha = $level;
}
else
{
throw new InvalidArgumentException(WandGetExceptionString($this->underPixelWand));
}
}
/**
* Set the under colour of the text (a block of colour that sits just beneath the text -
* can be useful for trimming)
*
* @throws InvalidArgumentException|UnexpectedValueException
* @param string $colour ImageMagick Colour String
* @return void
* @author Kieran Whitbread
**/
public function setUnderColour($colour)
{
PixelSetColor($this->underPixelWand, $colour);
if (WandGetExceptionType($this->underPixelWand) == MW_UndefinedException)
{
$this->underColour = $colour;
DrawSetTextUnderColor($this->drawingWand, $this->underPixelWand);
if (WandGetExceptionType($this->drawingWand) != MW_UndefinedException)
{
throw new UnexpectedValueException(WandGetExceptionString($this->drawingWand));
}
}
else
{
throw new InvalidArgumentException(WandGetExceptionString($this->underPixelWand));
}
}
}
