Skip to content

Commit

Permalink
visible Watermark:
Browse files Browse the repository at this point in the history
- Upload GUI for customized watermark, in settings.php
- drop down menu for choosing watermark level, in file settings.php
- Backend logic for generating and applying the watermark, in Run.php
- Database update in survey_runs, in files:  schema.sql, 039_add_watermark_settings.sql
- Datebase update in Docker
- update composer.json
- add default Watermark
- Action endpoint that handles the request for uploaded watermark, in AdminRunController.php
- Backend logic for uploading customized watermark, in Run.php

-
  • Loading branch information
tobiasjutzi committed Feb 26, 2024
1 parent a4fecc4 commit f3dbc68
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 46 deletions.
24 changes: 24 additions & 0 deletions application/Controller/AdminRunController.php
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,30 @@ private function uploadFilesAction() {
return $this->sendResponse();
}

private function uploadWatermarkAction() {
$run = $this->run;

if (!empty($_FILES['uploaded_files'])) {
if ($run->uploadWatermark($_FILES['uploaded_files'])) {
alert('<strong>Success.</strong> The files were uploaded.', 'alert-success');
if (!empty($run->messages)) {
alert(implode(' ', $run->messages), 'alert-info');
}

$this->request->redirect(admin_run_url($run->name, 'settings#watermark'));
} else {
alert('<strong>Sorry, files could not be uploaded.</strong><br /> ' . nl2br(implode("\n", $run->errors)), 'alert-danger');
}
} elseif ($this->request->isHTTPPostRequest()) {
alert('The size of your request exceeds the allowed limit. Please report this to administrators indicating the size of your files.', 'alert-danger');
}
// http://localhost/formr/admin/run/run25/settings#watermark
$this->setView('run/settings');
return $this->sendResponse();
}



private function viewImageAction() {
$original = $this->request->str('original');
$new = $this->request->str('new');
Expand Down
226 changes: 195 additions & 31 deletions application/Model/Run.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class Run extends Model {
"use_material_design", "expire_cookie",
"expire_cookie_value", "expire_cookie_unit",
"expiresOn",
"watermark_method", "watermark_content",
"watermark_method", "watermark_content", "watermark_path",
);
public $renderedDescAndFooterAlready = false;
public $expiresOn = null;
Expand Down Expand Up @@ -120,7 +120,7 @@ protected function load() {
return;
}

$columns = "id, user_id, created, modified, name, api_secret_hash, public, cron_active, cron_fork, locked, header_image_path, title, description, description_parsed, footer_text, footer_text_parsed, public_blurb, public_blurb_parsed, privacy, privacy_parsed, tos, tos_parsed, imprint, imprint_parsed, custom_css_path, custom_js_path, osf_project_id, use_material_design, expire_cookie, expiresOn, watermark_method, watermark_content";
$columns = "id, user_id, created, modified, name, api_secret_hash, public, cron_active, cron_fork, locked, header_image_path, title, description, description_parsed, footer_text, footer_text_parsed, public_blurb, public_blurb_parsed, privacy, privacy_parsed, tos, tos_parsed, imprint, imprint_parsed, custom_css_path, custom_js_path, osf_project_id, use_material_design, expire_cookie, expiresOn, watermark_method, watermark_content, watermark_path";
$where = $this->id ? array('id' => $this->id) : array('name' => $this->name);
$vars = $this->db->findRow('survey_runs', $where, $columns);

Expand Down Expand Up @@ -238,6 +238,7 @@ public function create($options) {
'footer_text_parsed' => "Remember to add your contact info here! Contact the <a href='mailto:[email protected]'>study administration</a> in case of questions.",
'watermark_method' => "none",
'watermark_content' => "formr.org",
'watermark_path' => "non",
));
$this->id = $this->db->pdo()->lastInsertId();
$this->name = $name;
Expand Down Expand Up @@ -266,7 +267,42 @@ public function getUploadedFiles() {
'text/csv' => '.csv', 'text/css' => '.css', 'text/tab-separated-values' => '.tsv', 'text/plain' => '.txt'
);

public function getWatermarkPath() {
return $this->db->select('watermark_path')
->from('survey_runs')
->where(array('id' => $this->id))
->fetchAll();
}

public function uploadFiles($files) {
function blendWatermarkWithImage($scaledWatermark, $image, $overlayX, $overlayY, $scaledWatermarkWidth, $scaledWatermarkHight){
for ($x = 0; $x < $scaledWatermarkWidth; $x++) {
for ($y = 0; $y < $scaledWatermarkHight; $y++) {

$watermarkPixel = imagecolorat($scaledWatermark, $x, $y);
$imagePixel = imagecolorat($image, $overlayX + $x, $overlayY + $y);

$imageR = ($imagePixel >> 16) & 0xFF;
$imageG = ($imagePixel >> 8) & 0xFF;
$imageB = $imagePixel & 0xFF;
$imageAlpha = ($imagePixel >> 24) & 0xFF;

$watermarkAlpha = ($watermarkPixel >> 24) & 0xFF;

$imageFactor = 0.1;
$watermarkFactor = 1 - $imageFactor;

$newR = (int)($imageFactor * $imageR + $watermarkFactor * (($watermarkPixel >> 16) & 0xFF));
$newG = (int)($imageFactor * $imageG + $watermarkFactor * (($watermarkPixel >> 8) & 0xFF));
$newB = (int)($imageFactor * $imageB + $watermarkFactor * ($watermarkPixel & 0xFF));

$newColor = imagecolorallocatealpha($scaledWatermark, $newR, $newG, $newB, $watermarkAlpha);
imagesetpixel($scaledWatermark, $x, $y, $newColor);
}
}
return $scaledWatermark;
}

$max_size_upload = Config::get('admin_maximum_size_of_uploaded_files');
// make lookup array
$existing_files = $this->getUploadedFiles();
Expand Down Expand Up @@ -317,52 +353,180 @@ public function uploadFiles($files) {
} else {
$this->errors[] = __("Unable to move uploaded file '%s' to storage location.", $files['name'][$i]);
}

if($fileSaved) {
// generate watermark version of image via python script if enabled in settings
if (($mime === 'image/jpeg' || $mime === 'image/png' || $mime === 'image/gif') && $this->watermark_method != "none") {
$scriptPath = '/var/www/formr.org/scripts/watermark/main.py';
$originalImage = escapeshellarg($destination_path);
$watermarkedImage = $destination_path; // overwrite original file
$content = escapeshellarg($this->watermark_content);

// watermarking method, default to sift method
$method = "sift";
if(str_contains($this->watermark_method, "blind")) {
$method = "blind";
}

$cmd = "/usr/bin/python3 $scriptPath embed -i $originalImage -o " . escapeshellarg($watermarkedImage) . " -w '$content' -m '$method'";
exec($cmd, $output, $return_var);

// cli returns 0 if successful, 1 if failed and 2 lines of output if successful
if ($return_var === 0 && count($output) === 2) {
// watermarking was successful
$watermark_content = $output[1];

// write additional watermark data to file (used for later detection of the watermark)
$file = fopen($watermarkedImage . "_watermarkdata", 'w');
fwrite($file, $watermark_content);
fclose($file);
} else {
// watermarking failed
$this->errors[] = __("Unable to watermark uploaded file '%s'.", $files['name'][$i]);
$this->db->update('survey_runs', array(
'watermark_path' => (string) $this->getWatermarkPath()[0]['watermark_path']
), array(
'id' => 1 // Annahme: Die Spalte 'id' identifiziert den aktuellen Lauf
));


if (($mime === 'image/jpeg' || $mime === 'image/png' || $mime === 'image/gif') && ($this->watermark_method == 'only_visible'
|| $this->watermark_method == 'visible_and_sift' || $this->watermark_method == 'visible_and_blind')) {
if ($this->getWatermarkPath()[0]['watermark_path'] == 'non') {

$image = imagecreatefrompng($destination_path);
$default_watermark = APPLICATION_ROOT . 'webroot/assets/watermark/default_uni_muenster.png';
$watermark = imagecreatefrompng($default_watermark);

$imageWidth = imagesx($image);
$imageHeight = imagesy($image);

$watermarkWidth = imagesx($watermark);
$watermarkHeight = imagesy($watermark);

$overlayX = $imageWidth - $watermarkWidth - round($imageWidth * 0.02);
$overlayY = $imageHeight - $watermarkHeight - round($imageWidth * 0.02);

imagecopy($image, $watermark, $overlayX, $overlayY, 0, 0, $watermarkWidth, $watermarkHeight);
imagepng($image, $destination_path);
} else {
$image = imagecreatefrompng($destination_path);
$imageWidth = imagesx($image);
$imageHeight = imagesy($image);

$watermarkPathArray = $this->getWatermarkPath();
$watermarkPath = (string)$watermarkPathArray[0]['watermark_path'];
$watermark = imagecreatefrompng($watermarkPath);

$watermarkWidth = imagesx($watermark);
$watermarkHeight = imagesy($watermark);


$targetWidth = round($imageWidth * 0.1); // 10% der Breite des Hauptbildes
$targetHeight = round($targetWidth * ($watermarkHeight / $watermarkWidth)); // Beibehaltung der Proportionen

$scaledWatermark = imagescale($watermark, $targetWidth, $targetHeight);

$scaledWatermarkWidth = imagesx($scaledWatermark);
$scaledWatermarkHight = imagesy($scaledWatermark);


$overlayX = $imageWidth - $targetWidth - round($imageWidth * 0.02);
$overlayY = $imageHeight - $targetHeight - round($imageWidth * 0.02);

$scaledWatermark = blendWatermarkWithImage($scaledWatermark, $image, $overlayX, $overlayY, $scaledWatermarkWidth, $scaledWatermarkHight);

imagecopy($image, $scaledWatermark, $overlayX, $overlayY, 0, 0, $targetWidth, $targetHeight);
imagepng($image, $destination_path);

}
}


// generate watermark version of image via python script if enabled in settings
if (($mime === 'image/jpeg' || $mime === 'image/png' || $mime === 'image/gif') && ($this->watermark_method == 'only_sift' ||
$this->watermark_method == 'only_blind' || $this->watermark_method == 'visible_and_sift' || $this->watermark_method == 'visible_and_blind')) {
$scriptPath = '/var/www/formr.org/scripts/waermark/main.py';
$originalImage = escapeshellarg($destination_path);
$watermarkedImage = $destination_path; // overwrite original file
$content = escapeshellarg($this->watermark_content);

// watermarking method, default to sift method
$method = "sift";
if(str_contains($this->watermark_method, "blind")) {
$method = "blind";
}

$cmd = "/usr/bin/python3 $scriptPath embed -i $originalImage -o " . escapeshellarg($watermarkedImage) . " -w '$content' -m '$method'";
exec($cmd, $output, $return_var);

// cli returns 0 if successful, 1 if failed and 2 lines of output if successful
if ($return_var === 0 && count($output) === 2) {
// watermarking was successful
$watermark_content = $output[1];

// write additional watermark data to file (used for later detection of the watermark)
$file = fopen($watermarkedImage . "_watermarkdata", 'w');
fwrite($file, $watermark_content);
fclose($file);
} else {
// watermarking failed
$this->errors[] = __("Unable to watermark uploaded file '%s'.", $files['name'][$i]);
}
}

$this->db->insert_update('survey_uploaded_files', array(
'run_id' => $this->id,
'created' => mysql_now(),
'original_file_name' => $original_file_name,
'new_file_path' => $new_file_path,
), array(
), array(
'modified' => mysql_now()
));

}

}

return empty($this->errors);
}

public function uploadWatermark($files) {

$max_size_upload = Config::get('admin_maximum_size_of_uploaded_files');
// make lookup array
$existing_files = $this->getUploadedFiles();
$files_by_names = array();
foreach ($existing_files as $existing_file) {
$files_by_names[$existing_file['original_file_name']] = $existing_file['new_file_path'];
}

// loop through files and modify them if necessary
for ($i = 0; $i < count($files['tmp_name']); $i++) {
// validate if any error occured on upload
if ($files['error'][$i]) {
$this->errors[] = __("An error occured uploading file '%s'. ERROR CODE: PFUL-%d", $files['name'][$i], $files['error'][$i]);
continue;
}

// validate file size
$size = (int) $files['size'][$i];
if (!$size || ($size > $max_size_upload * 1048576)) {
$this->errors[] = __("The file '%s' is too big or the size could not be determined. The allowed maximum size is %d megabytes.", $files['name'][$i], round($max_size_upload, 2));
continue;
}

// validate mime type
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($files['tmp_name'][$i]);
if (!isset($this->file_endings[$mime])) {
$this->errors[] = __('The file "%s" has the MIME type %s and is not allowed to be uploaded.', $files['name'][$i], $mime);
continue;
}

// validation was OK
$original_file_name = $files['name'][$i];
if (isset($files_by_names[$original_file_name])) {
// override existing path
$new_file_path = $files_by_names[$original_file_name];
$this->messages[] = __('The file "%s" was overriden.', $original_file_name);
} else {
$new_file_path = 'assets/tmp/admin/' . crypto_token(33, true) . $this->file_endings[$mime];}

$fileSaved = false;

// save file
$destination_path = APPLICATION_ROOT . 'webroot/' . $new_file_path;
if (move_uploaded_file($files['tmp_name'][$i], $destination_path)) {
$fileSaved = true;
} else {
$this->errors[] = __("Unable to move uploaded file '%s' to storage location.", $files['name'][$i]);
}
if($fileSaved) {

$this->db->update('survey_runs', array(
'watermark_path' => $destination_path
), array(
'id' => $this->id
));
}

}
return empty($this->errors);
}

public function deleteFile($id, $filename) {
$where = array('id' => (int) $id, 'original_file_name' => $filename);
$filepath = $this->db->findValue('survey_uploaded_files', $where, 'new_file_path');
Expand Down
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"atrox/haikunator": "1.*",
"paragonie/halite": "^4",
"phpoffice/phpspreadsheet": "^1.24",
"phpmailer/phpmailer": "6.*",
"ext-gd": "*",
"phpmailer/phpmailer": "6.*",
"robthree/twofactorauth": "2.1.0",
"chillerlan/php-qrcode": "^5.0"
Expand Down
5 changes: 4 additions & 1 deletion docker/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -679,4 +679,7 @@ CREATE TABLE `survey_settings` (
`value` text COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `setting` (`setting`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE survey_runs
ADD watermark_path varchar(255) DEFAULT 'non';
1 change: 1 addition & 0 deletions sql/patches/039_add_watermark_settings.sql
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
ALTER TABLE survey_runs
ADD watermark_method ENUM('none', 'only_visible', 'only_sift', 'only_blind', 'visible_and_sift', 'visible_and_blind') NOT NULL DEFAULT 'none',
ADD watermark_content varchar(255) NOT NULL DEFAULT "formr.org";
ADD watermark_path varchar(255) DEFAULT 'non';
5 changes: 4 additions & 1 deletion sql/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -675,4 +675,7 @@ CREATE TABLE `survey_settings` (
`value` text COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `setting` (`setting`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE survey_runs
ADD watermark_path varchar(255) DEFAULT 'non';
Loading

0 comments on commit f3dbc68

Please sign in to comment.