Index: trunk/admin/derivatives.php
===================================================================
--- trunk/admin/derivatives.php	(revision 12831)
+++ trunk/admin/derivatives.php	(revision 12851)
@@ -29,4 +29,5 @@
 {
   $pderivatives = $_POST['d'];
+  $pwatermark = $_POST['w'];
 
   // step 1 - sanitize HTML input
@@ -87,8 +88,53 @@
       $prev_h = intval($pderivative['h']);
     }
-  }
+    
+    $v = intval($pderivative['sharpen']);
+    if ($v<0 || $v>100)
+    {
+      $errors[$type]['sharpen'] = '[0..100]';
+    }
+    $v = intval($pderivative['quality']);
+    if ($v<=0 || $v>100)
+    {
+      $errors[$type]['quality'] = '(0..100]';
+    }
+  }
+  $v = intval($pwatermark['xpos']);
+  if ($v<0 || $v>100)
+  {
+    $errors['watermark']['xpos'] = '[0..100]';
+  }
+  $v = intval($pwatermark['ypos']);
+  if ($v<0 || $v>100)
+  {
+    $errors['watermark']['ypos'] = '[0..100]';
+  }
+  $v = intval($pwatermark['opacity']);
+  if ($v<=0 || $v>100)
+  {
+    $errors['watermark']['opacity'] = '(0..100]';
+  }
+
+
   // step 3 - save data
   if (count($errors)==0)
   {
+    $watermark = new WatermarkParams();
+    $watermark->file = $pwatermark['file'];
+    $watermark->xpos = intval($pwatermark['xpos']);
+    $watermark->ypos = intval($pwatermark['ypos']);
+    $watermark->xrepeat = intval($pwatermark['xrepeat']);
+    $watermark->opacity = intval($pwatermark['opacity']);
+    $watermark->min_size = array(intval($pwatermark['minw']),intval($pwatermark['minh']));
+    
+    $old_watermark = ImageStdParams::get_watermark();
+    $watermark_changed = 
+      $watermark->file != $old_watermark->file
+      || $watermark->xpos != $old_watermark->xpos
+      || $watermark->ypos != $old_watermark->ypos
+      || $watermark->xrepeat != $old_watermark->xrepeat
+      || $watermark->opacity != $old_watermark->opacity;
+    ImageStdParams::set_watermark($watermark);
+    
     $enabled = ImageStdParams::get_defined_type_map();
     $disabled = @unserialize( @$conf['disabled_derivatives'] );
@@ -107,9 +153,13 @@
         $new_params = new DerivativeParams(
             new SizingParams( 
-              array($pderivative['w'],$pderivative['h']),
+              array(intval($pderivative['w']), intval($pderivative['h'])),
               round($pderivative['crop'] / 100, 2),
-              array($pderivative['minw'],$pderivative['minh'])
+              array(intval($pderivative['minw']), intval($pderivative['minh']))
               ) 
           );
+        $new_params->sharpen = intval($pderivative['sharpen']);
+        $new_params->quality = intval($pderivative['quality']);
+        ImageStdParams::apply_global($new_params);
+        
         if (isset($enabled[$type]))
         {
@@ -124,4 +174,20 @@
           if ( $same && $new_params->sizing->max_crop != 0 
               && !size_equals($old_params->sizing->min_size, $new_params->sizing->min_size) )
+          {
+            $same = false;
+          }
+
+          if ( $same && 
+              ( $new_params->sharpen != $old_params->sharpen
+              || $new_params->quality > $old_params->quality)
+             )
+          {
+            $same = false;
+          }
+          
+          if ($same &&
+            ( $new_params->use_watermark != $old_params->use_watermark 
+             || $new_params->use_watermark && $watermark_changed )
+            )
           {
             $same = false;
@@ -183,4 +249,5 @@
   {
     $template->assign('derivatives', $pderivatives);
+    $template->assign('watermark', $pwatermark);
     $template->assign('ferrors', $errors);
   }
@@ -225,9 +292,35 @@
         $tpl_var['minw'] = $tpl_var['minh'] = "";
       }
+      $tpl_var['sharpen'] = $params->sharpen;
+      $tpl_var['quality'] = $params->quality;
     }
     $tpl_vars[$type]=$tpl_var;
   }
   $template->assign('derivatives', $tpl_vars);
+  
+  $wm = ImageStdParams::get_watermark();
+  $template->assign('watermark', array(
+      'file' => $wm->file,
+      'minw' => $wm->min_size[0],
+      'minh' => $wm->min_size[1],
+      'xpos' => $wm->xpos,
+      'ypos' => $wm->ypos,
+      'xrepeat' => $wm->xrepeat,
+      'opacity' => $wm->opacity,
+    ));
 }
+
+$watermark_files = array();
+foreach (glob(PHPWG_ROOT_PATH.'themes/default/watermarks/*.png') as $file)
+{
+  $watermark_files[] = substr($file, strlen(PHPWG_ROOT_PATH));
+}
+$watermark_filemap = array( '' => '---' );
+foreach( $watermark_files as $file)
+{
+  $display = basename($file);
+  $watermark_filemap[$file] = $display;
+}
+$template->assign('watermark_files', $watermark_filemap);
 
 $template->set_filename('derivatives', 'derivatives.tpl');
Index: trunk/admin/include/image.class.php
===================================================================
--- trunk/admin/include/image.class.php	(revision 12831)
+++ trunk/admin/include/image.class.php	(revision 12851)
@@ -42,4 +42,8 @@
 
   function resize($width, $height);
+  
+  function sharpen($amount);
+  
+  function compose($overlay, $x, $y, $opacity);
 
   function write($destination_filepath);
@@ -257,4 +261,29 @@
 
     return $rotation;
+  }
+
+  /** Returns a normalized convolution kernel for sharpening*/
+  static function get_sharpen_matrix($amount)
+  {
+		// Amount should be in the range of 18-10
+		$amount = round(abs(-18 + ($amount * 0.08)), 2);
+
+		$matrix = array
+		(
+			array(-1,   -1,    -1),
+			array(-1, $amount, -1),
+			array(-1,   -1,    -1),
+		);
+    
+    $norm = array_sum(array_map('array_sum', $matrix));
+
+    for ($i=0; $i<3; $i++)
+    {
+      $line = & $matrix[$i];
+      for ($j=0; $j<3; $j++)
+        $line[$j] /= $norm;
+    }
+
+		return $matrix;
   }
 
@@ -398,4 +427,16 @@
   }
 
+  function sharpen($amount)
+  {
+    $m = pwg_image::get_sharpen_matrix($amount);
+		return  $this->image->convolveImage($m);
+  }
+  
+  function compose($overlay, $x, $y, $opacity)
+  {
+    // todo
+    return false;
+  }
+
   function write($destination_filepath)
   {
@@ -416,14 +457,15 @@
   var $commands = array();
 
-  function __construct($source_filepath, $imagickdir='')
-  {
+  function __construct($source_filepath)
+  {
+    global $conf;
     $this->source_filepath = $source_filepath;
-    $this->imagickdir = $imagickdir;
-
-    $command = $imagickdir.'identify -format "%wx%h" "'.realpath($source_filepath).'"';
+    $this->imagickdir = $conf['ext_imagick_dir'];
+
+    $command = $this->imagickdir.'identify -format "%wx%h" "'.realpath($source_filepath).'"';
     @exec($command, $returnarray);
     if(!is_array($returnarray) or empty($returnarray[0]) or !preg_match('/^(\d+)x(\d+)$/', $returnarray[0], $match))
     {
-      die("[External ImageMagick] Corrupt image");
+      die("[External ImageMagick] Corrupt image\n" . var_export($returnarray, true));
     }
 
@@ -477,4 +519,29 @@
     $this->add_command('filter', 'Lanczos');
     $this->add_command('resize', $width.'x'.$height.'!');
+    return true;
+  }
+
+  function sharpen($amount)
+  {
+    $m = pwg_image::get_sharpen_matrix($amount);
+    
+    $param ='convolve "'.count($m).':';
+    foreach ($m as $line)
+    {
+      $param .= ' ';
+      $param .= implode(',', $line);
+    }
+    $param .= '"';
+    $this->add_command('morphology', $param);
+    return true;
+  }
+  
+  function compose($overlay, $x, $y, $opacity)
+  {
+    $param = 'compose dissolve -define compose:args='.$opacity;
+    $param .= ' '.escapeshellarg(realpath($overlay->image->source_filepath));
+    $param .= ' -gravity NorthWest -geometry +'.$x.'+'.$y;
+    $param .= ' -composite';
+    $this->add_command($param);
     return true;
   }
@@ -497,4 +564,6 @@
     $exec .= ' "'.realpath($dest['dirname']).'/'.$dest['basename'].'"';
     @exec($exec, $returnarray);
+    
+    //echo($exec);
     return is_array($returnarray);
   }
@@ -612,4 +681,31 @@
   }
 
+  function sharpen($amount)
+  {
+    $m = pwg_image::get_sharpen_matrix($amount);
+		return imageconvolution($this->image, $m, 1, 0);
+  }
+  
+  function compose($overlay, $x, $y, $opacity)
+  {
+    $ioverlay = $overlay->image->image;
+    /* A replacement for php's imagecopymerge() function that supports the alpha channel
+    See php bug #23815:  http://bugs.php.net/bug.php?id=23815 */
+
+    $ow = imagesx($ioverlay);
+    $oh = imagesy($ioverlay);
+      
+		// Create a new blank image the site of our source image
+		$cut = imagecreatetruecolor($ow, $oh);
+
+		// Copy the blank image into the destination image where the source goes
+		imagecopy($cut, $this->image, 0, 0, $x, $y, $ow, $oh);
+
+		// Place the source image in the destination image
+		imagecopy($cut, $ioverlay, 0, 0, 0, 0, $ow, $oh);
+		imagecopymerge($this->image, $cut, $x, $y, 0, 0, $ow, $oh, $opacity);
+    return true;
+  }
+
   function write($destination_filepath)
   {
Index: trunk/admin/themes/default/template/derivatives.tpl
===================================================================
--- trunk/admin/themes/default/template/derivatives.tpl	(revision 12831)
+++ trunk/admin/themes/default/template/derivatives.tpl	(revision 12851)
@@ -24,4 +24,40 @@
 
 <form method="post" id="derviativesForm">
+<fieldset>
+<legend>{'Watermark'|@translate}</legend>
+
+
+<select name="w[file]" id="wSelect">
+	{html_options options=$watermark_files selected=$watermark.file}
+</select>
+
+<p><img id="wImg"></img></p>
+
+<label>{'Min Width'|@translate}
+	<input type="text" name="w[minw]" value="{$watermark.minw}"{if isset($ferrors.watermark.minw)}class="dError"{/if}> 
+</label>
+
+<label>{'Min Height'|@translate}
+	<input type="text" name="w[minh]" value="{$watermark.minh}"{if isset($ferrors.watermark.minh)}class="dError"{/if}> 
+</label>
+
+<label>{'X Position'|@translate}
+	<input type="text" name="w[xpos]" value="{$watermark.xpos}"{if isset($ferrors.watermark.xpos)}class="dError"{/if}> 
+%</label>
+
+<label>{'Y Position'|@translate}
+	<input type="text" name="w[ypos]" value="{$watermark.ypos}"{if isset($ferrors.watermark.ypos)}class="dError"{/if}> 
+%</label>
+
+<label>{'X Repeat'|@translate}
+	<input type="text" name="w[xrepeat]" value="{$watermark.xrepeat}"{if isset($ferrors.watermark.xrepeat)}class="dError"{/if}> 
+</label>
+
+<label>{'Opacity'|@translate}
+	<input type="text" name="w[opacity]" value="{$watermark.opacity}"{if isset($ferrors.watermark.opacity)}class="dError"{/if}> 
+</label>
+
+</fieldset>
+
 <table class="table2">
 	<thead>
@@ -34,4 +70,6 @@
 		<td>{'Min Width'|@translate}</td>
 		<td>{'Min Height'|@translate}</td>
+		<td>{'Sharpen'|@translate} (%)</td>
+		<td>{'Quality'|@translate} (%)</td>
 	</tr>
 	</thead>
@@ -66,5 +104,12 @@
 			{if isset($ferrors.$type.minh)}<span class="dErrorDesc" title="{$ferrors.$type.minh}">!</span>{/if}
 		{/if}</td>
-
+		<td>
+			<input type="text" name="d[{$type}][sharpen]" value="{$d.sharpen}"{if isset($ferrors.$type.sharpen)}class="dError"{/if}>
+			{if isset($ferrors.$type.sharpen)}<span class="dErrorDesc" title="{$ferrors.$type.sharpen}">!</span>{/if}
+		</td>
+		<td>
+			<input type="text" name="d[{$type}][quality]" value="{$d.quality}"{if isset($ferrors.$type.quality)}class="dError"{/if}>
+			{if isset($ferrors.$type.quality)}<span class="dErrorDesc" title="{$ferrors.$type.quality}">!</span>{/if}
+		</td>
 	</tr>
 	{/foreach}
@@ -77,3 +122,17 @@
 	jQuery(this).removeClass("dError");
 } );
+
+function onWatermarkChange()
+{
+	var val = jQuery("#wSelect").val();
+	if (val.length) {
+		jQuery("#wImg").attr('src', {/literal}'{$ROOT_URL}'{literal}+val).show();
+	}
+	else {
+		jQuery("#wImg").hide();
+	}
+}
+
+onWatermarkChange();
+jQuery("#wSelect").bind("change", onWatermarkChange );
 {/literal}{/footer_script}
