Skip to content

Instantly share code, notes, and snippets.

@jonathonbyrdziak
Created March 15, 2012 18:43
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jonathonbyrdziak/2045951 to your computer and use it in GitHub Desktop.
Save jonathonbyrdziak/2045951 to your computer and use it in GitHub Desktop.
A Wordpress class that allows you to place watermarks on your images.
<?php
/**
* The constants defined here are used as a fallback to whatever options you
* end up specifying specifically.
*
* @var constant string
*/
define('WATERMARK_SOURCE', ABSPATH.'/wp-content/themes/mytheme/images/fb_thumb_watermark.png');
define('WATERMARK_ORIENTATION', 'bottom right');
define('WATERMARK_PADDING', 5);
define('WATERMARK_OPACITY', 90);
define('WATERMARK_BACKUP', true);
/**
* Preparing your theme to save watermarks on every image is simple. First
* do so by activating support for watermarks. The default setting will take a backup
* of your original upload and then apply a watermark to the primary image. This
* causes every image size crunched by wordpress to display the watermark as well.
*
* `add_theme_support('watermarks');`
*
* Additionally, you may specify individual sizes that you would like watermarks applied to.
* By stating 'all' as a parameter, bo backup will be taken, however the primary image
* will also not be watermarked. Every crunched image size of the original will receive
* a watermark. This process is slower then the default, but more accurate.
*
* `add_watermark_on_size('all');`
*
*/
add_theme_support('watermarks');
add_watermark_on_size('original', array(
'watermark' => ABSPATH.'/wp-content/themes/mytheme/images/master_watermark.png',
'orientation' => 'bottom center',
'opacity' => 90,
'extendcanvas' => true,
'horizontalline'=> true,
'backup' => true,
));
add_watermark_on_size('all', array(
'watermark' => ABSPATH.'/wp-content/themes/mytheme/images/master_watermark.png',
'opacity' => 90,
));
add_watermark_on_size(150, array(
'watermark' => ABSPATH.'/wp-content/themes/mytheme/images/fb_thumb_watermark_80.png',
'orientation' => 'center center',
'padding' => 5,
));
/**
* Method is designed to add a string to the image, this is not part of the class
* just something that i needed on my last project
*
* @param array $args
* @param object $instance
*/
add_filter('redrokk_watermark_args', 'redrokk_imageline', 20, 2);
function redrokk_imageline( $args, $instance )
{
if ($instance->_pData['x'] != 150) {
// creating the image line
$line = imagecolorallocate(
$line,
imagecreate($instance->_pData['x'], $instance->_pData['y']),
0, 0, 0);
// merging the line with the primary image
$y = ceil($instance->getDestinationY() + ($instance->_wData['y'] /2));
imagelinethick(
$args['primary'],
0, //x1
$y, //y1
$instance->_pData['x'], //x2
$y, //y2
$line,
$instance->_wData['y'] + $instance->padding
);
}
return $args;
}
function imagelinethick($image, $x1, $y1, $x2, $y2, $color, $thick = 1)
{
/* this way it works well only for orthogonal lines
imagesetthickness($image, $thick);
return imageline($image, $x1, $y1, $x2, $y2, $color);
*/
if ($thick == 1) {
return imageline($image, $x1, $y1, $x2, $y2, $color);
}
$t = $thick / 2 - 0.5;
if ($x1 == $x2 || $y1 == $y2) {
return imagefilledrectangle($image, round(min($x1, $x2) - $t), round(min($y1, $y2) - $t), round(max($x1, $x2) + $t), round(max($y1, $y2) + $t), $color);
}
$k = ($y2 - $y1) / ($x2 - $x1); //y = kx + q
$a = $t / sqrt(1 + pow($k, 2));
$points = array(
round($x1 - (1+$k)*$a), round($y1 + (1-$k)*$a),
round($x1 - (1-$k)*$a), round($y1 - (1+$k)*$a),
round($x2 + (1+$k)*$a), round($y2 - (1-$k)*$a),
round($x2 + (1-$k)*$a), round($y2 + (1+$k)*$a),
);
imagefilledpolygon($image, $points, 4, $color);
return imagepolygon($image, $points, 4, $color);
}
// using api
$filepath = watermark_image( $image, $watermark = NULL, $orientation = 'bottom right', $padding = 5, $opacity = 90 );
// using api as OOP
// specifically watermarking a single image
$water = redrokk_watermark_class::getInstance();
$water->set('primary', site_url('wp-content/uploads/2012/02/anuploadedimage.jpg'));
$water->set('watermark', site_url('wp-content/themes/mytheme/images/logo.png'));
$water->set('orientation', 'bottom left'); // or any combination thereof
//save the primary file over the top of itself
$path = $water->save();
//save the primary file with a new filename
$path = $water->save( 'newFileName' );
//show me the results
header('content-type: image/jpeg');
echo $water->getCompleted(); // outputs the image without saving it
die();
<?php
/**
* @Author Anonymous
* @link http://www.redrokk.com
* @Package Wordpress
* @SubPackage RedRokk Library
* @copyright Copyright (C) 2011+ Redrokk Interactive Media
*
* @version 0.1
*/
defined('ABSPATH') or die('cannot load directly');
/**
* Function prepares the current theme for watermarking
*
* @return string
*/
add_action('init', 'theme_supports_watermarks', 100);
function theme_supports_watermarks()
{
if (!current_theme_supports('watermarks')) return false;
add_filter('update_attached_file', 'watermark_image', 1, 1);
add_filter('image_make_intermediate_size', 'redrokk_watermarked_thumbs', 1, 1);
add_filter('wp_generate_attachment_metadata', 'redrokk_watermarked_original', 20, 2);
}
/**
*
*
* @param integer $size
*/
function add_watermark_on_size( $size, $args = array() )
{
global $watermark;
if ($watermark === null) {
$watermark = array();
}
if (!isset($watermark[$size])) {
$watermark[$size] = array();
}
$watermark[$size][] = $args;
return;
}
/**
* Function will overlay the watermark onto the original image
*
* @param int $attachment_id Attachment Id to process.
* @param string $file Filepath of the Attached image.
* @return mixed Metadata for attachment.
*/
function redrokk_watermarked_original( $metadata, $attachment_id )
{
global $watermark;
if (isset($watermark['original']))
{
foreach ((array)$watermark['original'] as $args)
{
$water = redrokk_watermark_class::getInstance(
ABSPATH.'wp-content'.DIRECTORY_SEPARATOR.'uploads'.DIRECTORY_SEPARATOR.$metadata['file'],
$args
);
$paths = $water->save();
}
}
return $metadata;
}
/**
* Function will overlay the watermark onto the specific image size
*
* @param $resized_file
*/
function redrokk_watermarked_thumbs( $resized_file )
{
global $watermark;
if ($watermark !== null)
{
list($width, $height, $type, $attr) = getimagesize($resized_file);
// if the size matches then watermark this image
$w = in_array($width, array_keys($watermark));
if ($w || in_array('all', array_keys($watermark)))
{
foreach ((array)$watermark[ $w ?$width :'all'] as $args)
{
$water = redrokk_watermark_class::getInstance($resized_file, $args);
$paths = $water->save();
}
}
}
return $resized_file;
}
/**
* Function will overlay the given watermark on the image and
* then save the new file, overwriting the given image
*
* @param string $image
* @param string $watermark
* @param string $orientation
* @param int $padding
* @param int $opacity
*/
function watermark_image( $image, $watermark = NULL, $orientation = NULL, $padding = NULL, $opacity = NULL )
{
global $watermark;
// close if theres a specific size
if ($watermark !== null) return $image;
//get the real path
$image = str_replace(site_url(), ABSPATH, $image);
$path = pathinfo( $image );
$backup = $path['dirname'].DIRECTORY_SEPARATOR
. $path['filename']
. '-original' . '.' . $path['extension'];
// making sure that we don't mess up any important images
if (!file_exists( $backup ))
{
if (!copy( $image, $backup ))
{
// define('WATERMARK_BACKUP', false);
if (!defined('WATERMARK_BACKUP'))
{
trigger_error(__FUNCTION__.': Could not create a backup of the original,'
. " to override define('WATERMARK_BACKUP', false);");
return $image;
}
elseif (WATERMARK_BACKUP)
{
trigger_error(__FUNCTION__.': Could not create a backup of the original.');
return $image;
}
}
} // just making sure that we dont add the watermark more then once
else
{
@copy( $backup, $image );
}
// setting the stage
$args = array();
if ($watermark !== NULL) {
$args['watermark'] = $watermark;
}
if ($orientation !== NULL) {
$args['orientation'] = $orientation;
}
if ($padding !== NULL) {
$args['padding'] = $padding;
}
if ($opacity !== NULL) {
$args['opacity'] = $opacity;
}
$water = redrokk_watermark_class::getInstance($image, $args);
$path = $water->save();
return $path['file'];
}
/**
* @author Anonymous
* @tutorial https://gist.github.com/2045951
*
*/
class redrokk_watermark_class
{
/**
* Contains the original watermark image
*
* The file should be a PNG-8 format file, not PNG-24.
* There’s a bug in the current version of GD, which doesn’t support
* PNG-24 correctly.
*
* @var string
*/
var $watermark;
/**
* Contains a block of information about the image
*
* @var string
*/
var $_wData = array();
/**
* Contains the original image to watermark
*
* @var string
*/
var $primary;
/**
* Contains a block of information about the image
*
* @var string
*/
var $_pData = array();
/**
* Contains the orientation for the watermark. System defaults to the bottom
* right.
*
* @example
* bottom
* top
* left
* right
*
* @var string
*/
var $orientation = 'bottom right';
/**
* How much padding between the watermark and the edges
*
* @var string|array
*/
var $padding = 5;
/**
* The opacity strength to apply when merging. 0 is hidden and 100 is full opacity.
*
* @var int
*/
var $opacity = 90;
/**
* We only need to run the merge once.
*
* @var boolean
*/
var $_merged = false;
/**
* The relation from the top
*
* @var string
*/
var $top = 'bottom';
/**
* The relation from the left
*
* @var string
*/
var $left = 'right';
/**
* Parameters determines whether or not the system will use smart scaling
* and adjust the size of the watermark, according to the image size.
*
* @var boolean
*/
var $scale = true;
/**
* Extends the canvas beyond the image for the watermark
*
* @var boolean
*/
var $extendcanvas = false;
/**
* Adds a background line behind the watermark
*
* @var boolean
*/
var $horizontalline = false;
/**
* Shall we save an original?
*
* @var boolean
*/
var $backup = false;
/**
* Constructor.
*
*/
function __construct( $options = array() )
{
// define('WATERMARK_SOURCE', '/path/to/file.png');
if (defined('WATERMARK_SOURCE')) {
$this->set('watermark', WATERMARK_SOURCE);
}
//define('WATERMARK_ORIENTATION', 'bottom right');
if (defined('WATERMARK_ORIENTATION')) {
$this->set('orientation', WATERMARK_ORIENTATION);
}
//define('WATERMARK_PADDING', 5);
if (defined('WATERMARK_PADDING')) {
$this->set('padding', WATERMARK_PADDING);
}
//define('WATERMARK_OPACITY', 90);
if (defined('WATERMARK_OPACITY')) {
$this->set('opacity', WATERMARK_OPACITY);
}
// initializing
$this->setProperties($options);
}
/**
* Function saves the newly created image into the uploads folder and returns
* an array which includes the file path.
*
* @see wp_upload_bits(); http://codex.wordpress.org/Function_Reference/wp_upload_bits
*
* @param string $path
*
* @return array
*/
function save( $path = NULL )
{
do_action(get_class().'::save', $this);
//merging if we have not yet merged
$bits = $this->getWatermarkedImageResource();
if ($path === NULL) {
$path = $this->primary;
}
if ($this->backup)
{
rename( $path,
$this->_pData['path'].'/'.$this->_pData['name'].".original.".$this->_pData['ext'] );
}
$parts = pathinfo( $path );
$name = $this->_pData['name'].'.'.$parts['extension'];
$wp_filetype = wp_check_filetype( $name );
if (!$wp_filetype['ext']) {
return array( 'error' => __( 'Invalid file type' ) );
}
if (!wp_mkdir_p( dirname( $path ) )) {
$message = sprintf( __( 'Unable to create directory %s. Is its parent directory writable by the server?' ), dirname( $new_file ) );
return array( 'error' => $message );
}
$ifp = @ fopen( $path, 'wb' );
if (!$ifp) {
return array( 'error' => sprintf( __( 'Could not write file %s' ), $path ) );
}
@fwrite( $ifp, $bits );
fclose( $ifp );
clearstatcache();
// Set correct file permissions
$stat = @ stat( dirname( $path ) );
$perms = $stat['mode'] & 0007777;
$perms = $perms & 0000666;
@ chmod( $path, $perms );
clearstatcache();
// Compute the URL
$url = str_replace(ABSPATH, site_url().'/', $path);
$options = array( 'file' => $path, 'url' => $url, 'error' => false );
if (0) {
echo '<pre> $options : ';
var_export($options);
echo "\r".'$this : ';
print_r(get_object_vars($this));
echo '</pre>';
die();
}
return $options;
}
/**
* Method merges the watermark image with the primary image to be watermarked,
* saves the final image and returns the path
*
* @notice use of the PNG format; GD 2.0+ has removed compatibility with GIF images
*
* @see imagecopymerge
* int imagecopymerge (
* resource dst_im,
* resource src_im,
* int dst_x,
* int dst_y,
* int src_x,
* int src_y,
* int src_w,
* int src_h,
* int pct )
*
* @tutorial
* Copy a part of src_im onto dst_im starting at the x,y coordinates src_x,
* src_y with a width of src_w and a height of src_h. The portion defined will
* be copied onto the x,y coordinates, dst_x and dst_y. The two images will be
* merged according to pct which can range from 0 to 100.
*
*/
function merge()
{
// merge the image if it has not yet been merged
if (!$this->_merged)
{
// additional adjustments
$this->canvassize();
$this->imageline();
// offering others an opportunity
$args = apply_filters('redrokk_watermark_args', array(
'primary' => $this->_pData['img'],
'watermark' => $this->_wData['img'],
'dst_x' => $this->getDestinationX(),
'dst_y' => $this->getDestinationY(),
'src_x' => 0,
'src_y' => 0,
'src_w' => $this->_wData['x'],
'src_h' => $this->_wData['y'],
'opacity' => $this->opacity
), $this);
$this->_merged = call_user_func_array('imagecopymerge', $args);
}
return $this->_merged;
}
/**
* Returns the image as a string
*
*/
function getWatermarkedImageResource()
{
$this->merge();
ob_start();
imagejpeg( $this->_pData['img'] );
return ob_get_clean();
}
/**
* Creates an image from a given file
*
* @param string $file
* @param string $type
*/
function getImageResource( $file, $type = 3 )
{
//initializing
$img = false;
if ($type === NULL) {
$size = getimagesize( $file );
$type = $size['mime'];
}
switch ($type) {
default:
case 3:
case '3':
case 'image/png':
$img = imagecreatefrompng($file);
break;
case 2:
case '2':
case 'image/jpeg':
$img = imagecreatefromjpeg($file);
break;
case 'image/gif':
$img = imagecreatefromgif($file);
break;
}
return $img;
}
/**
* Determines information about the given image and returns it as an array
*
* @param $file
*/
function getImageData( $file )
{
//initializing
if (!$size = getimagesize( $file )) return false;
$data = array();
list($width, $height, $type, $attr) = $size;
$data['x'] = $width;
$data['y'] = $height;
$data['mime'] = $type;
$path_parts = pathinfo( $file );
$data['path'] = $path_parts['dirname'];
$data['name'] = $path_parts['filename'];
$data['ext'] = $path_parts['extension'];
$data['img'] = $this->getImageResource( $file, $type );
return $data;
}
/**
* Returns the proper y value for the given orientation
*
*/
function getDestinationY()
{
// if top
if ($this->isTop())
{
return $this->getPaddingY();
}
// if center
elseif ($this->isYCenter())
{
return @ceil( ($this->_pData['y'] - $this->_wData['y']) / 2 );
}
// bottom is default
return $this->_pData['y'] - $this->_wData['y'] - $this->getPaddingY();
}
/**
* Returns the proper x value for the given orientation
*
*/
function getDestinationX()
{
// if left
if ($this->isLeft())
{
return $this->getPaddingX();
}
// if center
elseif ($this->isXCenter())
{
return @ceil( ($this->_pData['x'] - $this->_wData['x']) / 2 );
}
// right is default
return $this->_pData['x'] - $this->_wData['x'] - $this->getPaddingX();
}
/**
* Returns boolean value determining if the orientation asked for is
* an accurate orientation.
*
* @return boolean
*/
function isTop() {
return strpos(strtolower($this->top),'top') !== false;
}
function isYCenter() {
return strpos(strtolower($this->top),'center') !== false;
}
function isBottom() {
return strpos(strtolower($this->top),'bottom') !== false;
}
function isLeft() {
return strpos(strtolower($this->left),'left') !== false;
}
function isXCenter() {
return strpos(strtolower($this->left),'center') !== false;
}
function isRight() {
return strpos(strtolower($this->left),'right') !== false;
}
/**
* Returns the y padding integer
*
*/
function getPaddingY()
{
if (is_array($this->padding))
{
return $this->padding['y'];
}
return $this->padding;
}
/**
* Returns the x padding integer
*
*/
function getPaddingX()
{
if (is_array($this->padding))
{
return $this->padding['x'];
}
return $this->padding;
}
/**
* Sets the padding
*
*/
function setPadding( $value = NULL )
{
if ($value === NULL) return false;
return $this->padding = $value;
}
/**
* Sets the top and left orientation
*
*/
function setOrientation( $value = NULL )
{
if ($value === NULL) return false;
// initializing
$this->orientation = $value;
$parts = explode(' ', $this->orientation);
if (isset($parts[0])) {
$this->setTop( $parts[0] );
}
if (isset($parts[1])) {
$this->setLeft( $parts[1] );
}
}
/**
* Sets the orientation from the top
*
*/
function setTop( $value )
{
return $this->top = $value;
}
/**
* Sets the orientation from the left
*
*/
function setLeft( $value )
{
return $this->left = $value;
}
/**
* Sets the primary image and processes it
*
* @param string $value
*/
function setWatermark( $value = NULL )
{
if ($value === NULL) return false;
//get the real path
$value = str_replace(site_url(), ABSPATH, $value);
//remember image
$this->watermark = $value;
//process this image
if (!$this->_wData = $this->getImageData( $this->watermark )) {
trigger_error(__CLASS__.": image is not a valid resource ($this->watermark)");
}
}
/**
* Sets the primary image and processes it
*
* @param string $value
*/
function setPrimary( $value = NULL )
{
if ($value === NULL) return false;
//get the real path
$value = str_replace(site_url(), ABSPATH, $value);
//remember image
$this->primary = $value;
//process this image
if (!$this->_pData = $this->getImageData( $this->primary )) {
trigger_error(__CLASS__.": image is not a valid resource ($this->primary)");
}
}
/**
* Extends the canvas size of the image
*
*/
function canvassize()
{
if (!$this->extendcanvas) return false;
$extend = $this->_wData['y'] + $this->padding + 2;
// resizing the canvas
$blackcanvas = imagecreatetruecolor($this->_pData['x'], $this->_pData['y'] + $extend );
imagecopyresampled(
$blackcanvas, // Destination image link resource.
$this->_pData['img'], // Source image link resource.
0, // x-coordinate of destination point.
0, // y-coordinate of destination point.
0, // x-coordinate of source point.
0, // y-coordinate of source point.
$this->_pData['x'], // Destination width.
$this->_pData['y'], // Destination height.
$this->_pData['x'], // Source width.
$this->_pData['y'] // Source height.
);
$this->_pData['y'] = $this->_pData['y'] + $extend;
$this->_pData['img'] = $blackcanvas;
}
/**
* Creates the horizontal background line
*
*/
function imageline()
{
if (!$this->horizontalline) return false;
//initializing
$y = ceil($this->getDestinationY() + ($this->_wData['y'] /2));
$im = imagecreatetruecolor ( 140, 140 );
// creating the color
$color = imagecolorallocate($im, 255, 255, 255);
// merging the line with the primary image
$this->imagelinethick(
$this->_pData['img']
,0
,$y
,$this->_pData['x']
,$y
,$color
,$this->_wData['y'] + $this->padding + 2
);
return true;
}
/**
* This way it works well only for orthogonal lines imagesetthickness($image, $thick);
* return imageline($image, $x1, $y1, $x2, $y2, $color);
*
* @param $image
* @param $x1
* @param $y1
* @param $x2
* @param $y2
* @param $color
* @param $thick
*/
function imagelinethick($image, $x1, $y1, $x2, $y2, $color, $thick = 1)
{
if ($thick == 1)
return imageline($image, $x1, $y1, $x2, $y2, $color);
$t = $thick / 2 - 0.5;
if ($x1 == $x2 || $y1 == $y2)
return imagefilledrectangle($image, round(min($x1, $x2) - $t), round(min($y1, $y2) - $t), round(max($x1, $x2) + $t), round(max($y1, $y2) + $t), $color);
$k = ($y2 - $y1) / ($x2 - $x1); //y = kx + q
$a = $t / sqrt(1 + pow($k, 2));
$points = array(
round($x1 - (1+$k)*$a), round($y1 + (1-$k)*$a),
round($x1 - (1-$k)*$a), round($y1 - (1+$k)*$a),
round($x2 + (1+$k)*$a), round($y2 - (1-$k)*$a),
round($x2 + (1-$k)*$a), round($y2 + (1+$k)*$a),
);
imagefilledpolygon($image, $points, 4, $color);
return imagepolygon($image, $points, 4, $color);
}
/**
* Method to bind an associative array or object to the JTable instance.This
* method only binds properties that are publicly accessible and optionally
* takes an array of properties to ignore when binding.
*
* @param mixed $src An associative array or object to bind to the JTable instance.
* @param mixed $ignore An optional array or space separated list of properties to ignore while binding.
*
* @return boolean True on success.
*
* @link http://docs.joomla.org/JTable/bind
* @since 11.1
*/
public function bind($src, $ignore = array())
{
// If the source value is not an array or object return false.
if (!is_object($src) && !is_array($src))
{
trigger_error('Bind failed as the provided source is not an array.');
return false;
}
// If the source value is an object, get its accessible properties.
if (is_object($src))
{
$src = get_object_vars($src);
}
// If the ignore value is a string, explode it over spaces.
if (!is_array($ignore))
{
$ignore = explode(' ', $ignore);
}
// Bind the source value, excluding the ignored fields.
foreach ($this->getProperties() as $k => $v)
{
// Only process fields not in the ignore array.
if (!in_array($k, $ignore))
{
if (isset($src[$k]))
{
$this->$k = $src[$k];
}
}
}
return true;
}
/**
* Set the object properties based on a named array/hash.
*
* @param mixed $properties Either an associative array or another object.
*
* @return boolean
*
* @since 11.1
*
* @see set()
*/
public function setProperties($properties)
{
if (is_array($properties) || is_object($properties))
{
foreach ((array) $properties as $k => $v)
{
// Use the set function which might be overridden.
$this->set($k, $v);
}
return true;
}
return false;
}
/**
* Modifies a property of the object, creating it if it does not already exist.
*
* @param string $property The name of the property.
* @param mixed $value The value of the property to set.
*
* @return mixed Previous value of the property.
*
* @since 11.1
*/
public function set($property, $value = null)
{
$_property = 'set'.str_replace(' ', '', ucwords(str_replace('_', ' ', $property)));
if (method_exists($this, $_property)) {
return $this->$_property($value);
}
$previous = isset($this->$property) ? $this->$property : null;
$this->$property = $value;
return $previous;
}
/**
* Returns an associative array of object properties.
*
* @param boolean $public If true, returns only the public properties.
*
* @return array
*
* @see get()
*/
public function getProperties($public = true)
{
$vars = get_object_vars($this);
if ($public)
{
foreach ($vars as $key => $value)
{
if ('_' == substr($key, 0, 1))
{
unset($vars[$key]);
}
}
}
return $vars;
}
/**
* contains the current instance of this class
*
* @var object
*/
static $_instances = null;
/**
* Method is called when we need to instantiate this class
*
* @param array $options
*/
public static function getInstance( $primary, $options = array() )
{
if (!isset(self::$_instances[$primary]))
{
$options['primary'] = $primary;
$class = get_class();
self::$_instances[$primary] =& new $class($options);
}
return self::$_instances[$primary];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment