diff --git a/config/configuration.sample.yml b/config/configuration.sample.yml index 2d1742786d..b8ab08146a 100644 --- a/config/configuration.sample.yml +++ b/config/configuration.sample.yml @@ -30,8 +30,6 @@ main: binaries: ghostscript_binary: null php_binary: null - convert_binary: null - composite_binary: null swf_extract_binary: null pdf2swf_binary: null swf_render_binary: null diff --git a/lib/Alchemy/Phrasea/Command/Setup/Install.php b/lib/Alchemy/Phrasea/Command/Setup/Install.php index 6ed33cfc54..a78efc0a4c 100644 --- a/lib/Alchemy/Phrasea/Command/Setup/Install.php +++ b/lib/Alchemy/Phrasea/Command/Setup/Install.php @@ -254,8 +254,6 @@ class Install extends Command return array( 'php_binary' => $this->executableFinder->find('php'), 'phraseanet_indexer' => $this->executableFinder->find('phraseanet_indexer'), - 'convert_binary' => $this->executableFinder->find('convert'), - 'composite_binary' => $this->executableFinder->find('composite'), 'pdf2swf_binary' => $this->executableFinder->find('pdf2swf'), 'swf_extract_binary' => $this->executableFinder->find('swfextract'), 'swf_render_binary' => $this->executableFinder->find('swfrender'), diff --git a/lib/Alchemy/Phrasea/Controller/Setup.php b/lib/Alchemy/Phrasea/Controller/Setup.php index bb3936bf7b..0078f7ec3d 100644 --- a/lib/Alchemy/Phrasea/Controller/Setup.php +++ b/lib/Alchemy/Phrasea/Controller/Setup.php @@ -147,8 +147,6 @@ class Setup implements ControllerProviderInterface foreach (array( 'php_binary' => $request->request->get('binary_php'), 'phraseanet_indexer' => $request->request->get('binary_phraseanet_indexer'), - 'convert_binary' => $request->request->get('binary_convert'), - 'composite_binary' => $request->request->get('binary_composite'), 'swf_extract_binary' => $request->request->get('binary_swfextract'), 'pdf2swf_binary' => $request->request->get('binary_pdf2swf'), 'swf_render_binary' => $request->request->get('binary_swfrender'), diff --git a/lib/Alchemy/Phrasea/Core/Version.php b/lib/Alchemy/Phrasea/Core/Version.php index 010fb7b738..25cbf6b73d 100644 --- a/lib/Alchemy/Phrasea/Core/Version.php +++ b/lib/Alchemy/Phrasea/Core/Version.php @@ -18,7 +18,7 @@ namespace Alchemy\Phrasea\Core; */ class Version { - protected static $number = '3.8.0.a14'; + protected static $number = '3.8.0.a15'; protected static $name = 'Carnosaurus'; public static function getNumber() diff --git a/lib/Alchemy/Phrasea/Setup/Probe/BinariesProbe.php b/lib/Alchemy/Phrasea/Setup/Probe/BinariesProbe.php index b2424de839..67f90103eb 100644 --- a/lib/Alchemy/Phrasea/Setup/Probe/BinariesProbe.php +++ b/lib/Alchemy/Phrasea/Setup/Probe/BinariesProbe.php @@ -20,14 +20,12 @@ class BinariesProbe extends BinariesRequirements implements ProbeInterface { parent::__construct(array_filter(array( 'php_binary' => isset($binaries['php_binary']) ? $binaries['php_binary'] : null, - 'convert_binary' => isset($binaries['convert_binary']) ? $binaries['convert_binary'] : null, 'pdf2swf_binary' => isset($binaries['pdf2swf_binary']) ? $binaries['pdf2swf_binary'] : null, 'unoconv_binary' => isset($binaries['unoconv_binary']) ? $binaries['unoconv_binary'] : null, 'swf_extract_binary' => isset($binaries['swf_extract_binary']) ? $binaries['swf_extract_binary'] : null, 'swf_render_binary' => isset($binaries['swf_render_binary']) ? $binaries['swf_render_binary'] : null, 'mp4box_binary' => isset($binaries['mp4box_binary']) ? $binaries['mp4box_binary'] : null, 'pdftotext_binary' => isset($binaries['pdftotext_binary']) ? $binaries['pdftotext_binary'] : null, - 'composite_binary' => isset($binaries['composite_binary']) ? $binaries['composite_binary'] : null, 'ffmpeg_binary' => isset($binaries['ffmpeg_binary']) ? $binaries['ffmpeg_binary'] : null, 'ffprobe_binary' => isset($binaries['ffprobe_binary']) ? $binaries['ffprobe_binary'] : null, ))); diff --git a/lib/Alchemy/Phrasea/Setup/Requirements/BinariesRequirements.php b/lib/Alchemy/Phrasea/Setup/Requirements/BinariesRequirements.php index 71a9f909ba..1cd84c5fe5 100644 --- a/lib/Alchemy/Phrasea/Setup/Requirements/BinariesRequirements.php +++ b/lib/Alchemy/Phrasea/Setup/Requirements/BinariesRequirements.php @@ -58,48 +58,6 @@ class BinariesRequirements extends RequirementCollection implements RequirementI ); } - $convert = isset($binaries['convert_binary']) ? $binaries['convert_binary'] : $finder->find('convert'); - - $this->addRequirement( - null !== $convert && is_executable($convert), - 'ImageMagick Convert is required', - 'Please install ImageMagick' - ); - - if (null !== $convert) { - $output = null; - exec($convert . ' --version', $output); - $data = sscanf($output[0], 'Version: ImageMagick %d.%d.%d'); - $version = sprintf('%d.%d.%d', $data[0], $data[1], $data[2]); - - $this->addRequirement( - version_compare(static::IMAGICK_VERSION, $version, '<'), - sprintf('Convert version %s or higher is required (%s provided)', static::IMAGICK_VERSION, $version), - 'Please update to a more recent version' - ); - } - - $composite = isset($binaries['composite_binary']) ? $binaries['composite_binary'] : $finder->find('composite'); - - $this->addRequirement( - null !== $composite && is_executable($composite), - 'ImageMagick Composite is required', - 'Please install ImageMagick' - ); - - if (null !== $composite) { - $output = null; - exec($composite . ' --version', $output); - $data = sscanf($output[0], 'Version: ImageMagick %d.%d.%d'); - $version = sprintf('%d.%d.%d', $data[0], $data[1], $data[2]); - - $this->addRequirement( - version_compare(static::IMAGICK_VERSION, $version, '<'), - sprintf('Composite version %s or higher is required (%s provided)', static::IMAGICK_VERSION, $version), - 'Please update to a more recent version.' - ); - } - $exiftool = __DIR__ . '/../../../../../vendor/phpexiftool/exiftool/exiftool' . (defined('PHP_WINDOWS_VERSION_BUILD') ? '.exe' : ''); $this->addRequirement( diff --git a/lib/classes/API/V1/adapter.php b/lib/classes/API/V1/adapter.php index 5911b08e44..69f4e54a55 100644 --- a/lib/classes/API/V1/adapter.php +++ b/lib/classes/API/V1/adapter.php @@ -425,7 +425,6 @@ class API_V1_adapter extends API_V1_Abstract 'binary' => array( 'phpCli' => isset($binaries['php_binary']) ? $binaries['php_binary'] : null, 'phpIni' => $app['phraseanet.registry']->get('GV_PHP_INI'), - 'imagick' => $app['phraseanet.registry']->get('convert_binary'), 'swfExtract' => isset($binaries['swf_extract_binary']) ? $binaries['swf_extract_binary'] : null, 'pdf2swf' => isset($binaries['pdf2swf_binary']) ? $binaries['pdf2swf_binary'] : null, 'swfRender' => isset($binaries['swf_render_binary']) ? $binaries['swf_render_binary'] : null, diff --git a/lib/classes/patch/373.php b/lib/classes/patch/373.php index 0eb02093c5..49684ee42e 100644 --- a/lib/classes/patch/373.php +++ b/lib/classes/patch/373.php @@ -78,8 +78,6 @@ class patch_373 implements patchInterface $mapping = array( 'GV_cli' => 'php_binary', - 'GV_imagick' => 'convert_binary', - 'GV_pathcomposite' => 'composite_binary', 'GV_swf_extract' => 'swf_extract_binary', 'GV_pdf2swf' => 'pdf2swf_binary', 'GV_swf_render' => 'swf_render_binary', diff --git a/lib/classes/patch/3815.php b/lib/classes/patch/3815.php new file mode 100644 index 0000000000..5fce0b68ba --- /dev/null +++ b/lib/classes/patch/3815.php @@ -0,0 +1,57 @@ +release; + } + + /** + * {@inheritdoc} + */ + public function require_all_upgrades() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function concern() + { + return $this->concern; + } + + /** + * {@inheritdoc} + */ + public function apply(base $appbox, Application $app) + { + $binaries = $app['phraseanet.configuration']['binaries']; + unset($binaries['composite_binary'], $binaries['convert_binary']); + $app['phraseanet.configuration']['binaries'] = $binaries; + + return true; + } +} diff --git a/lib/classes/recordutils/image.php b/lib/classes/recordutils/image.php index 94d26a2960..89b8e337e0 100644 --- a/lib/classes/recordutils/image.php +++ b/lib/classes/recordutils/image.php @@ -10,83 +10,63 @@ */ use Alchemy\Phrasea\Application; -use Symfony\Component\Process\ProcessBuilder; +use Imagine\Image\ImagineInterface; +use Imagine\Image\Palette\RGB; +use Imagine\Image\Box; +use Imagine\Image\Point; +use Imagine\Exception\Exception as ImagineException; +use MediaVorus\Media\MediaInterface; +use MediaVorus\Media\Image; -/** - * - * - * @license http://opensource.org/licenses/gpl-3.0 GPLv3 - * @link www.phraseanet.com - */ class recordutils_image extends recordutils { - /** - * - * @param int $fontSize - * @param int $angle - * @param string $fontFace - * @param string $string - * @param int $width - * @return Array - */ - protected function wrap($fontSize, $angle, $fontFace, $string, $width) - { - $ret = array(); - - // str 'Op' used to calculate linespace - $testbox = imagettfbbox($fontSize, $angle, $fontFace, 'Op'); - $height = abs($testbox[1] - ($dy = $testbox[7])); - - foreach (explode("\n", $string) as $lig) { - if ($lig == '') { - $ret[] = ''; - } else { - $buff = ''; - foreach (explode(' ', $lig) as $wrd) { - $test = $buff . ($buff ? ' ' : '') . $wrd; - $testbox = imagettfbbox($fontSize, $angle, $fontFace, $test); - if (abs($testbox[2] - $testbox[0]) > $width) { - if ($buff == '') { - $ret[] = $test; - } else { - $ret[] = $buff; - $buff = $wrd; - } - } else { - $buff = $test; - } - } - if ($buff != '') - $ret[] = $buff; - } - } - - return(array('l' => $ret, 'h' => $height, 'dy' => $dy)); - } - - /** - * * @param Application $app * @param \media_subdef $subdef * - * @return string + * @return string The path to the stamped file */ public static function stamp(Application $app, \media_subdef $subdef) { + static $palette; + + if (null === $palette) { + $palette = new RGB(); + } + + $xmlToColor = function($attr, $ret = array(255, 255, 255, 0)) use ($palette) { + foreach (explode(',', $attr) as $i => $v) { + if ($i > 3) { + break; + } + $v = (int) (trim($v)); + if ($v >= 0 && ($v <= 100 || ($i < 3 && $v < 256))) { + $ret[$i] = $v; + } + } + $alpha = array_pop($ret); + + return $palette->color($ret, $alpha); + }; + $base_id = $subdef->get_record()->get_base_id(); - $binaries = $app['phraseanet.configuration']['binaries']; if ($subdef->get_type() !== \media_subdef::TYPE_IMAGE) { return $subdef->get_pathfile(); } - if ( ! $subdef->is_physically_present()) { + if (!$subdef->is_physically_present()) { return $subdef->get_pathfile(); } - if ( ! isset($binaries['convert_binary'])) { - return $subdef->get_pathfile(); + $rotation = null; + try { + $image = $app['mediavorus']->guess($subdef->get_pathfile()); + if (MediaInterface::TYPE_IMAGE === $image->getType()) { + $rotation = $image->getOrientation(); + } + } catch (Exception $e) { + // getting orientation failed but we don't care the reason } $domprefs = new DOMDocument(); @@ -100,205 +80,282 @@ class recordutils_image extends recordutils } $xpprefs = new DOMXPath($domprefs); - - $pathIn = $subdef->get_path() . $subdef->get_file(); - $pathOut = $subdef->get_path() . 'stamp_' . $subdef->get_file(); - $pathTmpStamp = $app['root.path'] . '/tmp/' . time() . '-stamptmp_' . $subdef->get_file(); - - if ($xpprefs->query('/baseprefs/stamp')->length == 0) { + $stampNodes = $xpprefs->query('/baseprefs/stamp'); + if ($stampNodes->length == 0) { return $subdef->get_pathfile(); } + $pathIn = $subdef->get_path() . $subdef->get_file(); + $pathOut = $subdef->get_path() . 'stamp_' . $subdef->get_file(); + $vars = $xpprefs->query('/baseprefs/stamp/*/var'); - for ($i = 0; $i < $vars->length; $i ++) { - $varval = ''; - $n = $vars->item($i); - switch (strtoupper($n->getAttribute('name'))) { - case 'DATE': - if ( ! ($format = $n->getAttribute('format'))) - $format = 'Y/m/d H:i:s'; - $varval = date($format); - @unlink($pathOut); - break; - case 'RECORD_ID': - $varval = $subdef->get_record()->get_record_id(); - break; + + // no way to cache when date changes + for ($i = 0; $i < $vars->length; $i++) { + if (strtoupper($vars->item($i)->getAttribute('name')) == 'DATE') { + @unlink($pathOut); + break; } - $n->parentNode->replaceChild($domprefs->createTextNode($varval), $n); } + // get from cache ? if (is_file($pathOut)) { return $pathOut; } - $fields = $xpprefs->query('/baseprefs/stamp/*/field'); - for ($i = 0; $i < $fields->length; $i ++) { - $fldval = ''; - $n = $fields->item($i); - $fieldname = $n->getAttribute('name'); - - $x = $sxxml->description->{$fieldname}; - if (is_array($x)) { - foreach ($x as $v) - $fldval .= ( $fldval ? '; ' : '') . (string) $v; - } else { - $fldval .= ( $fldval ? '; ' : '') . (string) $x; - } - $n->parentNode->replaceChild($domprefs->createTextNode($fldval), $n); + // open the document + $image_in = $app['imagine']->open($pathIn); + $image_size = $image_in->getSize(); + switch ($rotation) { + case Image::ORIENTATION_90: + $image_width = $image_size->getHeight(); + $image_height = $image_size->getWidth(); + $image_in->rotate(90); + $rotation = '90'; + break; + case Image::ORIENTATION_270: + $image_width = $image_size->getHeight(); + $image_height = $image_size->getWidth(); + $image_in->rotate(270); + break; + case Image::ORIENTATION_180: + $image_width = $image_size->getWidth(); + $image_height = $image_size->getHeight(); + $image_in->rotate(180); + break; + default: + $image_width = $image_size->getWidth(); + $image_height = $image_size->getHeight(); + break; } - $domprefs->normalizeDocument(); - - if ( ! ($tailleimg = @getimagesize($pathIn))) { - return false; - } - - $image_width = $tailleimg[0]; - $image_height = $tailleimg[1]; - - $text_xpos = 0; - $text_width = $image_width; - - $logofile = $app['root.path'] . '/config/stamp/' . $base_id; - $logopos = null; - $imlogo = null; // gd image + // open the logo $logo_phywidth = $logo_phyheight = 0; // physical size - $logo_reswidth = $logo_resheight = 0; // resized size + $logo_file = $app['root.path'] . '/config/stamp/' . $base_id; + try { + $logo_obj = $app['imagine']->open($logo_file); + $logo_size = $logo_obj->getSize(); + $logo_phywidth = $logo_size->getWidth(); + $logo_phyheight = $logo_size->getHeight(); + } catch (ImagineException $e) { - if (is_array($logosize = @getimagesize($logofile))) { - $logo_reswidth = $logo_phywidth = $logosize[0]; - $logo_resheight = $logo_phyheight = $logosize[1]; + } - $v = $xpprefs->query('/baseprefs/stamp/logo'); - if ($v->length > 0) { - $logopos = @strtoupper($v->item(0)->getAttribute('position')); - if (($logowidth = trim($v->item(0)->getAttribute('width'))) != '') { - if (substr($logowidth, -1) == '%') - $logo_reswidth = (int) ($logowidth * $image_width / 100); - else - $logo_reswidth = (int) $logowidth; - $logo_resheight = (int) ($logo_phyheight * - ($logo_reswidth / $logo_phywidth)); + $tables = array( + 'TOP' => array('h' => 0, 'rows' => array()), + 'TOP-OVER' => array('h' => 0, 'rows' => array()), + 'BOTTOM' => array('h' => 0, 'rows' => array()), + 'BOTTOM-OVER' => array('h' => 0, 'rows' => array()) + ); + + for ($istamp = 0; $istamp < $stampNodes->length; $istamp++) { + $stamp = $stampNodes->item($istamp); + + $stamp_background = $xmlToColor($stamp->getAttribute('background'), array(255, 255, 255, 0)); + + $stamp_position = strtoupper(trim($stamp->getAttribute('position'))); + if (!in_array($stamp_position, array('TOP', 'TOP-OVER', 'BOTTOM-OVER', 'BOTTOM'))) { + $stamp_position = 'BOTTOM'; + } + + // replace "var" nodes with their value + $vars = $xpprefs->query('*/var', $stamp); + for ($i = 0; $i < $vars->length; $i++) { + $varval = ''; + $n = $vars->item($i); + switch (strtoupper($n->getAttribute('name'))) { + case 'DATE': + if (!($format = $n->getAttribute('format'))) { + $format = 'Y/m/d H:i:s'; + } + $varval = date($format); + @unlink($pathOut); // no cache possible when date changes + break; + case 'RECORD_ID': + $varval = $subdef->get_record()->get_record_id(); + break; } + $n->parentNode->replaceChild($domprefs->createTextNode($varval), $n); + } - if (($logopos == 'LEFT' || $logopos == 'RIGHT') && - $logo_phywidth > 0 && $logo_phyheight > 0) { - switch ($logosize['mime']) { - case 'image/gif': - $imlogo = @imagecreatefromgif($logofile); - break; - case 'image/png': - $imlogo = @imagecreatefrompng($logofile); - break; - case 'image/jpeg': - case 'image/pjpeg': - $imlogo = @imagecreatefromjpeg($logofile); - break; + // replace "field" nodes with their values + $fields = $xpprefs->query('*/field', $stamp); + for ($i = 0; $i < $fields->length; $i++) { + $fldval = ''; + $n = $fields->item($i); + $fieldname = $n->getAttribute('name'); + + $x = $sxxml->description->{$fieldname}; + if (is_array($x)) { + foreach ($x as $v) { + $fldval .= ( $fldval ? '; ' : '') . (string) $v; } - if ($imlogo) { + } else { + $fldval .= ( $fldval ? '; ' : '') . (string) $x; + } + $n->parentNode->replaceChild($domprefs->createTextNode($fldval), $n); + } + + $domprefs->normalizeDocument(); + + $text_xpos = 0; + $text_width = $image_width; + + $logopos = null; + + // compute logo position / size + $logo_reswidth = 0; + $logo_resheight = 0; + if ($logo_phywidth > 0 && $logo_phyheight > 0) { + + $v = $xpprefs->query('logo', $stamp); + if ($v->length > 0) { + $logo_reswidth = $logo_phywidth; + $logo_resheight = $logo_phyheight; + $logopos = @strtoupper($v->item(0)->getAttribute('position')); + if (($logowidth = trim($v->item(0)->getAttribute('width'))) != '') { + if (substr($logowidth, -1) == '%') { + $logo_reswidth = (int) ($logowidth * $image_width / 100); + } else { + $logo_reswidth = (int) $logowidth; + } + $logo_resheight = (int) ($logo_phyheight * ($logo_reswidth / $logo_phywidth)); + } + + if ($logopos == 'LEFT' || $logopos == 'RIGHT') { if ($logo_reswidth > $image_width / 2) { // logo too large, resize please $logo_reswidth = (int) ($image_width / 2); - $logo_resheight = (int) ($logo_phyheight * - ($logo_reswidth / $logo_phywidth)); + $logo_resheight = (int) ($logo_phyheight * ($logo_reswidth / $logo_phywidth)); + } + $text_width -= $logo_reswidth; + if ($logopos == 'LEFT') { + $logo_xpos = 0; + $text_xpos = $logo_reswidth; + } else { + $text_xpos = 0; + $logo_xpos = ($image_width - $logo_reswidth); } - - $text_width -= ( $text_xpos = $logo_reswidth); } } } - } - $txth = 0; - $txtblock = array(); - $texts = $xpprefs->query('/baseprefs/stamp/text'); - $fontsize = "100%"; - for ($i = 0; $i < $texts->length; $i ++) { - if (($tmpfontsize = trim($texts->item($i)->getAttribute('size'))) != '') { - if (substr($tmpfontsize, -1) == '%') - $tmpfontsize = (int) ($tmpfontsize * $image_width / 4000); - else - $tmpfontsize = (int) $tmpfontsize; - $fontsize = $tmpfontsize; + + // compute text blocks + $txth = 0; + $txtblock = array(); + $texts = $xpprefs->query('text', $stamp); + $fontsize = "100%"; + for ($i = 0; $i < $texts->length; $i++) { + if (($tmpfontsize = trim($texts->item($i)->getAttribute('size'))) != '') { + if (substr($tmpfontsize, -1) == '%') { + $tmpfontsize = (int) ($tmpfontsize * $image_width / 4000); + } else { + $tmpfontsize = (int) $tmpfontsize; + } + $fontsize = $tmpfontsize; + } + + if ($fontsize < 2) { + $fontsize = 2; + } elseif ($fontsize > 300) { + $fontsize = 300; + } + + $txtline = $texts->item($i)->nodeValue; + + if ($txtline != '') { + $wrap = static::wrap($app['imagine'], $fontsize, 0, __DIR__ . '/arial.ttf', $txtline, $text_width); + $txtblock[] = array( + 'fontsize' => $fontsize, + 'fontcolor' => $xmlToColor($texts->item($i)->getAttribute('color'), array(0, 0, 0, 0)), + 'h' => $wrap['toth'], + 'lines' => $wrap['l'] + ); + $txth += $wrap['toth']; + } } - if ($fontsize < 2) - $fontsize = 2; - elseif ($fontsize > 300) - $fontsize = 300; + $stampheight = max($logo_resheight, $txth); - $txtline = $texts->item($i)->nodeValue; + if ($stamp_position == 'TOP-OVER' || $stamp_position == 'BOTTOM-OVER') { + if ($tables[$stamp_position]['h'] + $stampheight > $image_height) { + $stampheight = $image_height - $tables[$stamp_position]['h']; + } + } + if ($stampheight <= 0) { + continue; + } - if ($txtline != '') { - $txtlines = recordutils_image::wrap( - $fontsize, 0, __DIR__ . '/arial.ttf', $txtline, $text_width - ); + // create the block + $imfg = $app['imagine']->create(new Box($image_width, $stampheight), $stamp_background); - foreach ($txtlines['l'] as $txtline) { - $txtblock[] = array( - 'x' => $text_xpos, - 'dy' => $txtlines['dy'], - 'w' => $text_width, - 'h' => $txtlines['h'], - 't' => $txtline, - 's' => $fontsize + // copy the logo + if ($logo_reswidth > 0 && $logo_resheight > 0) { + if ($logo_reswidth != $logo_phywidth) { + $imfg->paste( + $logo_obj->copy()->resize(new Box($logo_reswidth, $logo_resheight)), + new Point($logo_xpos, 0) ); - $txth += $txtlines['h']; + } else { + $imfg->paste($logo_obj, new Point($logo_xpos, 0)); + } + } + + // fill with text + $draw = $imfg->draw(); + $txt_ypos = 0; + foreach ($txtblock as $block) { + $font = $app['imagine']->font(__DIR__ . '/arial.ttf', $block['fontsize'], $block['fontcolor']); + foreach ($block['lines'] as $line) { + if ($line['t'] != '') { + $draw->text($line['t'], $font, new Point(0, $txt_ypos), 0); + } + $txt_ypos += $line['h']; + } + } + + // memo into one of the 4 buffer + $tables[$stamp_position]['rows'][] = array( + 'x0' => 0, + 'y0' => $tables[$stamp_position]['h'], + 'w' => $image_width, + 'h' => $stampheight, + 'img' => $imfg + ); + + $tables[$stamp_position]['h'] += $stampheight; + } + + $newh = $tables['TOP']['h'] + $image_height + $tables['BOTTOM']['h']; + + // create the output image + $image_out = $app['imagine']->create(new Box($image_width, $newh), $palette->color("FFFFFF", 64)); + + // paste the input image into + $image_out->paste($image_in, new Point(0, $tables['TOP']['h'])); + + // fix the coordinates + foreach ($tables['TOP-OVER']['rows'] as $k => $row) { + $tables['TOP-OVER']['rows'][$k]['y0'] += $tables['TOP']['h']; + } + foreach ($tables['BOTTOM-OVER']['rows'] as $k => $row) { + $tables['BOTTOM-OVER']['rows'][$k]['y0'] += $tables['TOP']['h'] + $image_height - $tables['BOTTOM-OVER']['h']; + } + foreach ($tables['BOTTOM']['rows'] as $k => $row) { + $tables['BOTTOM']['rows'][$k]['y0'] += $tables['TOP']['h'] + $image_height; + } + + // paste blocks + foreach (array('TOP', 'TOP-OVER', 'BOTTOM-OVER', 'BOTTOM') as $ta) { + foreach ($tables[$ta]['rows'] as $row) { + if ($row['h'] > 0) { + $image_out->paste($row['img'], new Point($row['x0'], $row['y0'])); } } } - $stampheight = max($logo_resheight, $txth); - - $im = imagecreatetruecolor($image_width, $stampheight); - - $white = imagecolorallocate($im, 255, 255, 255); - imagefilledrectangle($im, 0, 0, $image_width, $stampheight, $white); - imagecolordeallocate($im, $white); - - if ($imlogo) { - if ($logo_reswidth != $logo_phywidth) { - imagecopyresampled($im, $imlogo, 0, 0, // dst_x, dst_y - 0, 0, // src_x, src_y - $logo_reswidth, // dst_w - $logo_resheight, // dst_h - $logo_phywidth, // src_w - $logo_phyheight // src_h - ); - } else { - imagecopy($im, $imlogo, 0, 0, // dst_x, dst_y - 0, 0, // src_x, src_y - $logo_phywidth, // src_w - $logo_phyheight // src_h - ); - } - } - - if (count($txtblock) >= 0) { - $black = imagecolorallocate($im, 0, 0, 0); - $txt_ypos = 0; //$txtblock[0]['h']; - foreach ($txtblock as $block) { - imagettftext($im, $block['s'], 0, $block['x'], $txt_ypos - $block['dy'], $black, __DIR__ . '/arial.ttf', $block['t']); - $txt_ypos += $block['h']; - } - imagecolordeallocate($im, $black); - } - imagejpeg($im, $pathTmpStamp, 80); - - imagedestroy($im); - - $newh = $image_height + $stampheight; - - $builder = ProcessBuilder::create(array($binaries['convert_binary'])); - $builder->add('-extent') - ->add($image_width . 'x' . $newh) - ->add('-draw') - ->add('image SrcOver 0,' . $image_height . ' ' . $image_width . ',' . $stampheight . '"' . $pathTmpStamp . '"') - ->add($pathIn) - ->add($pathOut); - - $builder->getProcess()->run(); - - unlink($pathTmpStamp); + // save the output + $image_out->save($pathOut); if (is_file($pathOut)) { return $pathOut; @@ -316,8 +373,13 @@ class recordutils_image extends recordutils */ public static function watermark(Application $app, \media_subdef $subdef) { + static $palette; + + if (null === $palette) { + $palette = new RGB(); + } + $base_id = $subdef->get_record()->get_base_id(); - $binaries = $app['phraseanet.configuration']['binaries']; if ($subdef->get_name() !== 'preview') { return $subdef->get_pathfile(); @@ -327,72 +389,155 @@ class recordutils_image extends recordutils return $subdef->get_pathfile(); } - if ( ! $subdef->is_physically_present()) { - return $subdef->get_pathfile(); + if (!$subdef->is_physically_present()) { + return false; } $pathIn = $subdef->get_path() . $subdef->get_file(); - $pathOut = $subdef->get_path() . 'watermark_' . $subdef->get_file(); - if (!is_file($pathIn)) { return false; } + $pathOut = $subdef->get_path() . 'watermark_' . $subdef->get_file(); + + // cache if (is_file($pathOut)) { return $pathOut; } - if (isset($binaries['composite_binary']) && - file_exists($app['root.path'] . '/config/wm/' . $base_id)) { + $in_image = $app['imagine']->open($pathIn); + $in_size = $in_image->getSize(); + $in_w = $in_size->getWidth(); + $in_h = $in_size->getHeight(); - $builder = ProcessBuilder::create(array( - $binaries['composite_binary'], - $app['root.path'] . '/config/wm/' . $base_id, - $pathIn, - '-strip', '-watermark', '90%', '-gravity', 'center', - $pathOut - )); + $wm_file = $app['root.path'] . '/config/wm/' . $base_id; + if (file_exists($wm_file)) { + $wm_image = $app['imagine']->open($wm_file); + $wm_size = $wm_image->getSize(); + $wm_w = $wm_size->getWidth(); + $wm_h = $wm_size->getHeight(); - $builder->getProcess()->run(); - } elseif (isset($binaries['convert_binary'])) { - $collname = phrasea::bas_labels($base_id, $app); - $tailleimg = @getimagesize($pathIn); - $max = ($tailleimg[0] > $tailleimg[1] ? $tailleimg[0] : $tailleimg[1]); + if (($wm_w / $wm_h) > ($in_w / $in_h)) { + $wm_size = $wm_size->widen($in_w); + } else { + $wm_size = $wm_size->heighten($in_h); + } + $wm_image->resize($wm_size); - $tailleText = (int) ($max / 30); + $in_image->paste($wm_image, new Point(($in_w - $wm_size->getWidth()) >> 1, ($in_h - $wm_size->getHeight()) >> 1))->save($pathOut); + } else { + $collname = $subdef->get_record()->get_collection()->get_name(); + $draw = $in_image->draw(); + $black = $palette->color("000000"); + $white = $palette->color("FFFFFF"); + $draw->line(new Point(0, 1), new Point($in_w - 2, $in_h - 1), $black); + $draw->line(new Point(1, 0), new Point($in_w - 1, $in_h - 2), $white); + $draw->line(new Point(0, $in_h - 2), new Point($in_w - 2, 0), $black); + $draw->line(new Point(1, $in_h - 1), new Point($in_w - 1, 1), $white); - if ($tailleText < 8) - $tailleText = 8; + $fsize = max(8, (int) (max($in_w, $in_h) / 30)); + $fonts = array( + $app['imagine']->font(__DIR__ . '/arial.ttf', $fsize, $black), + $app['imagine']->font(__DIR__ . '/arial.ttf', $fsize, $white) + ); + $testbox = $fonts[0]->box($collname, 0); + $tx_w = min($in_w, $testbox->getWidth()); + $tx_h = min($in_h, $testbox->getHeight()); - if ($tailleText > 12) - $decalage = 2; - else - $decalage = 1; + $x0 = max(1, ($in_w - $tx_w) >> 1); + $y0 = max(1, ($in_h - $tx_h) >> 1); + for ($i = 0; $i <= 1; $i++) { + $x = max(1, ($in_w >> 2) - ($tx_w >> 1)); + $draw->text($collname, $fonts[$i], new Point($x - $i, $y0 - $i), 0); + $x = max(1, $in_w - $x - $tx_w); + $draw->text($collname, $fonts[$i], new Point($x - $i, $y0 - $i), 0); - $builder = ProcessBuilder::create(array( - $binaries['convert_binary'], - '-fill', 'white', '-draw', 'line 0,0 ' . $tailleimg[0] . ',' . $tailleimg[1] . '', - '-fill', 'black', '-draw', 'line 1,0 ' . $tailleimg[0] + 1 . ',' . $tailleimg[1] . '', - '-fill', 'white', '-draw', 'line ' . $tailleimg[0] . ',0 0,' . $tailleimg[1] . '', - '-fill', 'black', '-draw', 'line ' . ($tailleimg[0] + 1) . ',0 0,' . $tailleimg[1] . '', - '-fill', 'white', '-gravity', 'NorthWest', '-pointsize', $tailleText, '-draw', 'text 0,0 ' . $collname, - '-fill', 'black', '-gravity', 'NorthWest', '-pointsize', $tailleText, '-draw', 'text ' . $decalage . ', 1 ' . $collname, - '-fill', 'white', '-gravity', 'center', '-pointsize', $tailleText, '-draw', 'text 0,0 ' . $collname, - '-fill', 'black', '-gravity', 'center', '-pointsize', $tailleText, '-draw', 'text ' . $decalage . ', 1 ' . $collname, - '-fill', 'white', '-gravity', 'SouthEast', '-pointsize', $tailleText, '-draw', 'text 0,0 ' . $collname, - '-fill', 'black', '-gravity', 'SouthEast', '-pointsize', $tailleText, '-draw', 'text ' . $decalage . ', 1 ' . $collname, - $pathIn, $pathOut - )); - - $process = $builder->getProcess(); - $process->run(); + $y = max(1, ($in_h >> 2) - ($tx_h >> 1)); + $draw->text($collname, $fonts[$i], new Point($x0 - $i, $y - $i), 0); + $y = max(1, $in_h - $y - $tx_h); + $draw->text($collname, $fonts[$i], new Point($x0 - $i, $y - $i), 0); + } } + $in_image->save($pathOut); + if (is_file($pathOut)) { return $pathOut; } return false; } + /** + * + * @param int $fontSize + * @param int $angle + * @param string $fontFace + * @param string $string + * @param int $width + * @return array + */ + protected static function wrap(ImagineInterface $imagine, $fontSize, $angle, $fontFace, $string, $width) + { + static $palette; + + if (null === $palette) { + $palette = new RGB(); + } + + // str 'Op' used to calculate linespace + $font = $imagine->font($fontFace, $fontSize, $palette->color("000000", 0)); + $testbox = $font->box("0p", $angle); + $height = $testbox->getHeight(); + $testbox = $font->box("M", $angle); // 1 em + $dy = $testbox->getHeight(); + $toth = 0; + $ret = array(); + + foreach (explode("\n", $string) as $lig) { + if ($lig == '') { + $ret[] = array('w' => 0, 'h' => $dy, 't' => ''); + $toth += $dy; + } else { + $twords = array(); + $iword = -1; + $lastc = ''; + for ($i = 0; $i < strlen($lig); $i++) { + $c = $lig[$i]; + if ($iword == -1 || (ctype_space($c) && !ctype_space($lastc))) { + $twords[++$iword] = array(($part = 0) => '', 1 => ''); + } + if (!ctype_space($c) && $part == 0) { + $part++; + } + $twords[$iword][$part] .= $lastc = $c; + } + if ($iword >= 0 && $twords[0][1] != '') { + $buff = ''; + $lastw = $lasth = 0; + foreach ($twords as $i => $wrd) { + $test = $buff . $wrd[0] . $wrd[1]; + $testbox = $font->box($test, $angle); + $w = $testbox->getWidth(); + $h = $testbox->getHeight(); + if ($i > 0 && $testbox->getWidth() > $width) { + $ret[] = array('w' => $lastw, 'h' => $lasth, 't' => $buff); + $toth += $lasth; + $buff = $wrd[1]; + } else { + $buff = $test; + } + $lastw = $w; + $lasth = $h; + } + if ($buff != '') { + $ret[] = array('w' => $lastw, 'h' => $lasth, 't' => $buff); + $toth += $lasth; + } + } + } + } + + return array('toth' => $toth, 'l' => $ret, 'h' => $height, 'dy' => $dy); + } } diff --git a/lib/conf.d/configuration.yml b/lib/conf.d/configuration.yml index faebf9dd06..c9f1f0159a 100644 --- a/lib/conf.d/configuration.yml +++ b/lib/conf.d/configuration.yml @@ -33,8 +33,6 @@ debugger: binaries: ghostscript_binary: null php_binary: null - convert_binary: null - composite_binary: null swf_extract_binary: null pdf2swf_binary: null swf_render_binary: null diff --git a/tests/classes/recordutils/imageTest.php b/tests/classes/recordutils/imageTest.php new file mode 100644 index 0000000000..0bb7c19a95 --- /dev/null +++ b/tests/classes/recordutils/imageTest.php @@ -0,0 +1,108 @@ +write_collection_pic( + self::$DI['app']['media-alchemyst'], + self::$DI['app']['filesystem'], + self::$DI['record_1']->get_collection(), + null, + \collection::PIC_WM + ); + + $path = recordutils_image::watermark(self::$DI['app'], self::$DI['record_1']->get_subdef('preview')); + + $this->assertTrue(0 === strpos(basename($path), 'watermark_')); + unlink($path); + } + + public function testWatermarkWithFile() + { + self::$DI['app']['phraseanet.appbox']->write_collection_pic( + self::$DI['app']['media-alchemyst'], + self::$DI['app']['filesystem'], + self::$DI['record_1']->get_collection(), + new SymfoFile(__DIR__ . '/../../files/logocoll.gif'), + \collection::PIC_WM + ); + + $path = recordutils_image::watermark(self::$DI['app'], self::$DI['record_1']->get_subdef('preview')); + + $this->assertTrue(0 === strpos(basename($path), 'watermark_')); + unlink($path); + } + + private function addStampConf(\collection $coll) + { + $domprefs = new DOMDocument(); + $domprefs->loadXML($coll->get_prefs()); + + $prefs = ' + + 0 + + + + Date: + Record_id: '; + + foreach ($coll->get_databox()->get_meta_structure() as $databox_field) { + $name = $databox_field->get_name(); + $prefs .= ''.$name.': ' . "\n"; + } + + $prefs .= ' + 1 + + +'; + + $newdom = new DOMDocument(); + $newdom->loadXML($prefs); + + $coll->set_prefs($newdom); + } + + public function testStampWithoutFile() + { + $this->addStampConf(self::$DI['record_1']->get_collection()); + + self::$DI['app']['phraseanet.appbox']->write_collection_pic( + self::$DI['app']['media-alchemyst'], + self::$DI['app']['filesystem'], + self::$DI['record_1']->get_collection(), + null, + \collection::PIC_STAMP + ); + + $path = recordutils_image::stamp(self::$DI['app'], self::$DI['record_1']->get_subdef('preview')); + + $this->assertTrue(0 === strpos(basename($path), 'stamp_')); + unlink($path); + } + + public function testStampWithFile() + { + $this->addStampConf(self::$DI['record_1']->get_collection()); + + self::$DI['app']['phraseanet.appbox']->write_collection_pic( + self::$DI['app']['media-alchemyst'], + self::$DI['app']['filesystem'], + self::$DI['record_1']->get_collection(), + new SymfoFile(__DIR__ . '/../../files/logocoll.gif'), + \collection::PIC_STAMP + ); + + $path = recordutils_image::stamp(self::$DI['app'], self::$DI['record_1']->get_subdef('preview')); + + $this->assertTrue(0 === strpos(basename($path), 'stamp_')); + unlink($path); + } +} \ No newline at end of file