From b4a3bb0c8ca8aa1391b448efeec3ea36e9498799 Mon Sep 17 00:00:00 2001 From: Aina Sitraka <35221835+aynsix@users.noreply.github.com> Date: Wed, 15 Mar 2023 16:26:25 +0300 Subject: [PATCH] PHRAS-3822 Prod : Improve Image WaterMarking (#4266) * admin improve watermark * unused * allow to use collection wm image to wm a subdef (set coll_wm in structure) * [skip ci] upload a wm for subdef (WIP to be continued ?) * allow to use a record as wm image (document) to wmark subdefs * cleanup --------- Co-authored-by: jygaulier --- lib/Alchemy/Phrasea/Media/Subdef/Image.php | 4 +- .../Media/Subdef/OptionType/EnumButton.php | 20 +++ .../Media/Subdef/OptionType/OptionType.php | 1 + lib/Alchemy/Phrasea/Media/SubdefGenerator.php | 169 +++++++++++++----- lib/classes/databox/subdef.php | 19 +- lib/classes/databox/subdefsStructure.php | 2 +- templates/web/admin/subdefs.html.twig | 8 +- 7 files changed, 173 insertions(+), 50 deletions(-) create mode 100644 lib/Alchemy/Phrasea/Media/Subdef/OptionType/EnumButton.php diff --git a/lib/Alchemy/Phrasea/Media/Subdef/Image.php b/lib/Alchemy/Phrasea/Media/Subdef/Image.php index e9ad88721b..92d49f60a0 100644 --- a/lib/Alchemy/Phrasea/Media/Subdef/Image.php +++ b/lib/Alchemy/Phrasea/Media/Subdef/Image.php @@ -24,6 +24,7 @@ class Image extends Provider const OPTION_ICODEC = 'icodec'; const OPTION_WATERMARK = 'watermark'; const OPTION_WATERMARKTEXT = 'watermarktext'; + const OPTION_WATERMARKRID = 'watermarkrid'; protected $options = []; @@ -37,8 +38,9 @@ class Image extends Provider $this->registerOption(new OptionType\Boolean($this->translator->trans('Flatten layers'), self::OPTION_FLATTEN, false)); $this->registerOption(new OptionType\Range($this->translator->trans('Quality'), self::OPTION_QUALITY, 0, 100, 75)); $this->registerOption(new OptionType\Enum('Image Codec', self::OPTION_ICODEC, array('jpeg', 'png', 'tiff'), 'jpeg')); - $this->registerOption(new OptionType\Boolean($this->translator->trans('Watermark'), self::OPTION_WATERMARK, false)); + $this->registerOption(new OptionType\EnumButton($this->translator->trans('Watermark'), self::OPTION_WATERMARK, array('no' => 'no', 'yes' => 'yes'), 'no')); $this->registerOption(new OptionType\Text($this->translator->trans('Watermark text'), self::OPTION_WATERMARKTEXT, '')); + $this->registerOption(new OptionType\Text($this->translator->trans('Watermark Record_id'), self::OPTION_WATERMARKRID, '')); } public function getType() diff --git a/lib/Alchemy/Phrasea/Media/Subdef/OptionType/EnumButton.php b/lib/Alchemy/Phrasea/Media/Subdef/OptionType/EnumButton.php new file mode 100644 index 0000000000..8d72b3ca45 --- /dev/null +++ b/lib/Alchemy/Phrasea/Media/Subdef/OptionType/EnumButton.php @@ -0,0 +1,20 @@ +logger; } - private function generateSubdef(\record_adapter $record, \databox_subdef $subdef_class, $pathdest) + private function generateSubdef(\record_adapter $record, databox_subdef $subdef_class, $pathdest) { $start = microtime(true); $destFile = null; @@ -296,12 +298,31 @@ class SubdefGenerator $image = $subdef_class->getSubdefType(); /** @var Boolean $wm */ $wm = $image->getOption(Subdef\Image::OPTION_WATERMARK); - if($wm->getValue()) { - + if($wm->getValue() === 'yes') { // bc to "text" mode // we must watermark the file - /** @var Text $wmt */ - $wmt = $image->getOption(Subdef\Image::OPTION_WATERMARKTEXT); - $this->wartermarkImageFile($pathdest, $wmt->getValue()); + $wm_text = null; + $wm_image = null; + + /** @var Text $opt */ + + $opt = $image->getOption(Subdef\Image::OPTION_WATERMARKTEXT); + if($opt && ($t = trim($opt->getValue())) !== '') { + $wm_text = $t; + } + + $opt = $image->getOption(Subdef\Image::OPTION_WATERMARKRID); + if($opt && ($rid = trim($opt->getValue())) !== '') { + try { + $wm_image = $subdef_class->getDatabox()->get_record($rid)->get_subdef('document')->getRealPath(); + } + catch (\Exception $e) { + $this->logger->error(sprintf('Getting wm image (record %d) failed with message %s', $rid, $e->getMessage())); + } + } + + if(!is_null($wm_text) || !is_null($wm_image)) { + $this->wartermarkImageFile($pathdest, $wm_text, $wm_image); + } } } @@ -324,13 +345,16 @@ class SubdefGenerator } } - private function wartermarkImageFile(string $filepath, string $watermarkText) + /** + * @param string $filepath + * @param string|null $watermarkText + * @param string|null $watermarkImage + */ + private function wartermarkImageFile(string $filepath, $watermarkText, $watermarkImage) { static $palette; - if (null === $palette) { - $palette = new RGB(); - } + /** @var Imagine $imagine */ $imagine = $this->getImagine(); $in_image = $imagine->open($filepath); @@ -338,43 +362,80 @@ class SubdefGenerator $in_w = $in_size->getWidth(); $in_h = $in_size->getHeight(); - $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); + $in_image_changed = false; - if($watermarkText) { - $fsize = max(8, (int)(max($in_w, $in_h) / 30)); - $fonts = [ - $imagine->font(__DIR__ . '/../../../../resources/Fonts/arial.ttf', $fsize, $black), - $imagine->font(__DIR__ . '/../../../../resources/Fonts/arial.ttf', $fsize, $white) - ]; - $testbox = $fonts[0]->box($watermarkText, 0); - $tx_w = min($in_w, $testbox->getWidth()); - $tx_h = min($in_h, $testbox->getHeight()); + if ($watermarkImage !== null && file_exists($watermarkImage)) { + $wm_image = $imagine->open($watermarkImage); + $wm_size = $wm_image->getSize(); + $wm_w = $wm_size->getWidth(); + $wm_h = $wm_size->getHeight(); - $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($watermarkText, $fonts[$i], new Point($x - $i, $y0 - $i)); - $x = max(1, $in_w - $x - $tx_w); - $draw->text($watermarkText, $fonts[$i], new Point($x - $i, $y0 - $i)); - - $y = max(1, ($in_h >> 2) - ($tx_h >> 1)); - $draw->text($watermarkText, $fonts[$i], new Point($x0 - $i, $y - $i)); - $y = max(1, $in_h - $y - $tx_h); - $draw->text($watermarkText, $fonts[$i], new Point($x0 - $i, $y - $i)); + 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); + + $in_image->paste($wm_image, new Point(($in_w - $wm_size->getWidth()) >> 1, ($in_h - $wm_size->getHeight()) >> 1)); + + $in_image_changed = true; } - $in_image->save($filepath); + if($watermarkText !== null) { + if (null === $palette) { + $palette = new RGB(); + } + + $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 ($watermarkText) { + $fsize = max(8, (int)(max($in_w, $in_h) / 30)); + $fonts = [ + $imagine->font(__DIR__ . '/../../../../resources/Fonts/arial.ttf', $fsize, $black), + $imagine->font(__DIR__ . '/../../../../resources/Fonts/arial.ttf', $fsize, $white) + ]; + $testbox = $fonts[0]->box($watermarkText, 0); + $tx_w = min($in_w, $testbox->getWidth()); + $tx_h = min($in_h, $testbox->getHeight()); + + $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($watermarkText, $fonts[$i], new Point($x - $i, $y0 - $i)); + $x = max(1, $in_w - $x - $tx_w); + $draw->text($watermarkText, $fonts[$i], new Point($x - $i, $y0 - $i)); + + $y = max(1, ($in_h >> 2) - ($tx_h >> 1)); + $draw->text($watermarkText, $fonts[$i], new Point($x0 - $i, $y - $i)); + $y = max(1, $in_h - $y - $tx_h); + $draw->text($watermarkText, $fonts[$i], new Point($x0 - $i, $y - $i)); + } + } + $in_image_changed = true; + } + + if($in_image_changed) { + $in_image->save($filepath); + } } - public function generateSubdefFromFile($pathSrc, \databox_subdef $subdef_class, $pathdest) + /** + * Used only by api V3 subdef generator service + * + * @param $pathSrc + * @param databox_subdef $subdef_class + * @param $pathdest + */ + public function generateSubdefFromFile($pathSrc, databox_subdef $subdef_class, $pathdest) { $start = microtime(true); $destFile = null; @@ -428,12 +489,32 @@ class SubdefGenerator $image = $subdef_class->getSubdefType(); /** @var Boolean $wm */ $wm = $image->getOption(Subdef\Image::OPTION_WATERMARK); - if($wm->getValue()) { + if($wm->getValue() === 'yes') { // bc to "text" mode // we must watermark the file - /** @var Text $wmt */ - $wmt = $image->getOption(Subdef\Image::OPTION_WATERMARKTEXT); - $this->wartermarkImageFile($pathdest, $wmt->getValue()); + $wm_text = null; + $wm_image = null; + + /** @var Text $opt */ + + $opt = $image->getOption(Subdef\Image::OPTION_WATERMARKTEXT); + if($opt && ($t = trim($opt->getValue())) !== '') { + $wm_text = $t; + } + + $opt = $image->getOption(Subdef\Image::OPTION_WATERMARKRID); + if($opt && ($rid = trim($opt->getValue())) !== '') { + try { + $wm_image = $subdef_class->getDatabox()->get_record($rid)->get_subdef('document')->getRealPath(); + } + catch (\Exception $e) { + $this->logger->error(sprintf('Getting wm image (record %d) failed with message %s', $rid, $e->getMessage())); + } + } + + if(!is_null($wm_text) || !is_null($wm_image)) { + $this->wartermarkImageFile($pathdest, $wm_text, $wm_image); + } } } diff --git a/lib/classes/databox/subdef.php b/lib/classes/databox/subdef.php index 39e4070b3c..6909a84d52 100644 --- a/lib/classes/databox/subdef.php +++ b/lib/classes/databox/subdef.php @@ -22,6 +22,9 @@ use Symfony\Component\Translation\TranslatorInterface; class databox_subdef { + /** @var databox|null */ + private $databox; + /** * The class type of the subdef * Is null or one of the CLASS_* constants @@ -67,8 +70,9 @@ class databox_subdef * * @return databox_subdef */ - public function __construct(SubdefType $type, SimpleXMLElement $sd, TranslatorInterface $translator) + public function __construct(SubdefType $type, SimpleXMLElement $sd, TranslatorInterface $translator, databox $databox = null) { + $this->databox = $databox; $this->subdef_group = $type; $this->class = (string)$sd->attributes()->class; $this->translator = $translator; @@ -113,6 +117,12 @@ class databox_subdef break; } } + + public function getDatabox() + { + return $this->databox; + } + /** * Build Image Subdef object depending the SimpleXMLElement * @@ -141,11 +151,14 @@ class databox_subdef $image->setOptionValue(Image::OPTION_FLATTEN, p4field::isyes($sd->flatten)); } if ($sd->watermark) { - $image->setOptionValue(Image::OPTION_WATERMARK, p4field::isyes($sd->watermark)); + $image->setOptionValue(Image::OPTION_WATERMARK, (string) $sd->watermark); } if ($sd->watermarktext) { $image->setOptionValue(Image::OPTION_WATERMARKTEXT, $sd->watermarktext); } + if ($sd->watermarkrid) { + $image->setOptionValue(Image::OPTION_WATERMARKRID, $sd->watermarkrid); + } return $image; } /** @@ -456,4 +469,4 @@ class databox_subdef { return $this->subdef_type->getOptions(); } -} \ No newline at end of file +} diff --git a/lib/classes/databox/subdefsStructure.php b/lib/classes/databox/subdefsStructure.php index a8d1a0218c..ffd6927b8a 100644 --- a/lib/classes/databox/subdefsStructure.php +++ b/lib/classes/databox/subdefsStructure.php @@ -107,7 +107,7 @@ class databox_subdefsStructure implements IteratorAggregate, Countable $group = $this->getSubdefGroup($subdefgroup_name); foreach ($subdefs as $sd) { - $group->addSubdef(new databox_subdef($group->getType(), $sd, $this->translator)); + $group->addSubdef(new databox_subdef($group->getType(), $sd, $this->translator, $this->databox)); } } } diff --git a/templates/web/admin/subdefs.html.twig b/templates/web/admin/subdefs.html.twig index 6deb1d8223..b94e27c043 100644 --- a/templates/web/admin/subdefs.html.twig +++ b/templates/web/admin/subdefs.html.twig @@ -62,7 +62,6 @@ $("#slidervalue" + section + name + defType + fieldname).val(value); } - function populate_values(section, name) { var config = JSON.parse('{{ config |json_encode|raw }}'), @@ -620,6 +619,13 @@ {% if selected %}checked="checked"{% endif %}/>{{ pot_value }} {% endfor %} + {% elseif option.getType() == constant('\\Alchemy\\Phrasea\\Media\\Subdef\\OptionType\\OptionType::TYPE_ENUM_BUTTON') %} +
+ {% for label, pot_value in option.getAvailableValues() %} + {{ label }} + {% endfor %} +
{% endif %}