mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-25 00:02:41 +01:00
Made Meme Generator
Summary: Fixed T2353 Test Plan: Checked whether same text generated the same URL Reviewers: epriestley Reviewed By: epriestley CC: aran, Korvin Maniphest Tasks: T2353 Differential Revision: https://secure.phabricator.com/D4551
This commit is contained in:
parent
f919f000e7
commit
fa19618d26
5 changed files with 151 additions and 0 deletions
BIN
resources/font/tuffy.ttf
Normal file
BIN
resources/font/tuffy.ttf
Normal file
Binary file not shown.
|
@ -925,6 +925,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorMacroEditController' => 'applications/macro/controller/PhabricatorMacroEditController.php',
|
'PhabricatorMacroEditController' => 'applications/macro/controller/PhabricatorMacroEditController.php',
|
||||||
'PhabricatorMacroEditor' => 'applications/macro/editor/PhabricatorMacroEditor.php',
|
'PhabricatorMacroEditor' => 'applications/macro/editor/PhabricatorMacroEditor.php',
|
||||||
'PhabricatorMacroListController' => 'applications/macro/controller/PhabricatorMacroListController.php',
|
'PhabricatorMacroListController' => 'applications/macro/controller/PhabricatorMacroListController.php',
|
||||||
|
'PhabricatorMacroMemeController' => 'applications/macro/controller/PhabricatorMacroMemeController.php',
|
||||||
'PhabricatorMacroReplyHandler' => 'applications/macro/mail/PhabricatorMacroReplyHandler.php',
|
'PhabricatorMacroReplyHandler' => 'applications/macro/mail/PhabricatorMacroReplyHandler.php',
|
||||||
'PhabricatorMacroTransaction' => 'applications/macro/storage/PhabricatorMacroTransaction.php',
|
'PhabricatorMacroTransaction' => 'applications/macro/storage/PhabricatorMacroTransaction.php',
|
||||||
'PhabricatorMacroTransactionComment' => 'applications/macro/storage/PhabricatorMacroTransactionComment.php',
|
'PhabricatorMacroTransactionComment' => 'applications/macro/storage/PhabricatorMacroTransactionComment.php',
|
||||||
|
@ -2308,6 +2309,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorMacroEditController' => 'PhabricatorMacroController',
|
'PhabricatorMacroEditController' => 'PhabricatorMacroController',
|
||||||
'PhabricatorMacroEditor' => 'PhabricatorApplicationTransactionEditor',
|
'PhabricatorMacroEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||||
'PhabricatorMacroListController' => 'PhabricatorMacroController',
|
'PhabricatorMacroListController' => 'PhabricatorMacroController',
|
||||||
|
'PhabricatorMacroMemeController' => 'PhabricatorMacroController',
|
||||||
'PhabricatorMacroReplyHandler' => 'PhabricatorMailReplyHandler',
|
'PhabricatorMacroReplyHandler' => 'PhabricatorMailReplyHandler',
|
||||||
'PhabricatorMacroTransaction' => 'PhabricatorApplicationTransaction',
|
'PhabricatorMacroTransaction' => 'PhabricatorApplicationTransaction',
|
||||||
'PhabricatorMacroTransactionComment' => 'PhabricatorApplicationTransactionComment',
|
'PhabricatorMacroTransactionComment' => 'PhabricatorApplicationTransactionComment',
|
||||||
|
|
|
@ -2,6 +2,18 @@
|
||||||
|
|
||||||
final class PhabricatorImageTransformer {
|
final class PhabricatorImageTransformer {
|
||||||
|
|
||||||
|
public function executeMemeTransform(
|
||||||
|
PhabricatorFile $file,
|
||||||
|
$upper_text,
|
||||||
|
$lower_text) {
|
||||||
|
$image = $this->applyMemeTo($file, $upper_text, $lower_text);
|
||||||
|
return PhabricatorFile::newFromFileData(
|
||||||
|
$image,
|
||||||
|
array(
|
||||||
|
'name' => 'meme-'.$file->getName(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
public function executeThumbTransform(
|
public function executeThumbTransform(
|
||||||
PhabricatorFile $file,
|
PhabricatorFile $file,
|
||||||
$x,
|
$x,
|
||||||
|
@ -134,6 +146,97 @@ final class PhabricatorImageTransformer {
|
||||||
return $this->saveImageDataInAnyFormat($dst, $file->getMimeType());
|
return $this->saveImageDataInAnyFormat($dst, $file->getMimeType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function applyMemeTo(
|
||||||
|
PhabricatorFile $file,
|
||||||
|
$upper_text,
|
||||||
|
$lower_text) {
|
||||||
|
$data = $file->loadFileData();
|
||||||
|
$img = imagecreatefromstring($data);
|
||||||
|
$phabricator_root = dirname(phutil_get_library_root('phabricator'));
|
||||||
|
$font_path = $phabricator_root.'/resources/font/tuffy.ttf';
|
||||||
|
$white = imagecolorallocate($img, 255, 255, 255);
|
||||||
|
$black = imagecolorallocate($img, 0, 0, 0);
|
||||||
|
$border_width = 3;
|
||||||
|
$font_max = 200;
|
||||||
|
$font_min = 5;
|
||||||
|
for ($i = $font_max; $i > $font_min; $i--) {
|
||||||
|
$fit = $this->doesTextBoundingBoxFitInImage(
|
||||||
|
$img,
|
||||||
|
$upper_text,
|
||||||
|
$i,
|
||||||
|
$font_path);
|
||||||
|
if ($fit['doesfit']) {
|
||||||
|
$x = ($fit['imgwidth'] - $fit['txtwidth']) / 2;
|
||||||
|
$y = $fit['txtheight'] + 10;
|
||||||
|
$this->makeImageWithTextBorder($img,
|
||||||
|
$i,
|
||||||
|
$x,
|
||||||
|
$y,
|
||||||
|
$white,
|
||||||
|
$black,
|
||||||
|
$border_width,
|
||||||
|
$font_path,
|
||||||
|
$upper_text);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ($i = $font_max; $i > $font_min; $i--) {
|
||||||
|
$fit = $this->doesTextBoundingBoxFitInImage($img,
|
||||||
|
$lower_text, $i, $font_path);
|
||||||
|
if ($fit['doesfit']) {
|
||||||
|
$x = ($fit['imgwidth'] - $fit['txtwidth']) / 2;
|
||||||
|
$y = $fit['imgheight'] - 10;
|
||||||
|
$this->makeImageWithTextBorder(
|
||||||
|
$img,
|
||||||
|
$i,
|
||||||
|
$x,
|
||||||
|
$y,
|
||||||
|
$white,
|
||||||
|
$black,
|
||||||
|
$border_width,
|
||||||
|
$font_path,
|
||||||
|
$lower_text);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this->saveImageDataInAnyFormat($img, $file->getMimeType());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeImageWithTextBorder($img, $font_size, $x, $y,
|
||||||
|
$color, $stroke_color, $bw, $font, $text) {
|
||||||
|
$angle = 0;
|
||||||
|
$bw = abs($bw);
|
||||||
|
for ($c1 = $x - $bw; $c1 <= $x + $bw; $c1++) {
|
||||||
|
for ($c2 = $y - $bw; $c2 <= $y + $bw; $c2++) {
|
||||||
|
if (!(($c1 == $x - $bw || $x + $bw) &&
|
||||||
|
$c2 == $y - $bw || $c2 == $y + $bw)) {
|
||||||
|
$bg = imagettftext($img, $font_size,
|
||||||
|
$angle, $c1, $c2, $stroke_color, $font, $text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imagettftext($img, $font_size, $angle,
|
||||||
|
$x , $y, $color , $font, $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function doesTextBoundingBoxFitInImage($img,
|
||||||
|
$text, $font_size, $font_path) {
|
||||||
|
// Default Angle = 0
|
||||||
|
$angle = 0;
|
||||||
|
|
||||||
|
$bbox = imagettfbbox($font_size, $angle, $font_path, $text);
|
||||||
|
$text_height = abs($bbox[3] - $bbox[5]);
|
||||||
|
$text_width = abs($bbox[0] - $bbox[2]);
|
||||||
|
return array(
|
||||||
|
"doesfit" => ($text_height * 1.05 <= imagesy($img)
|
||||||
|
&& $text_width * 1.05 <= imagesx($img)),
|
||||||
|
"txtwidth" => $text_width,
|
||||||
|
"txtheight" => $text_height,
|
||||||
|
"imgwidth" => imagesx($img),
|
||||||
|
"imgheight" => imagesy($img),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private function saveImageDataInAnyFormat($data, $preferred_mime = '') {
|
private function saveImageDataInAnyFormat($data, $preferred_mime = '') {
|
||||||
switch ($preferred_mime) {
|
switch ($preferred_mime) {
|
||||||
case 'image/gif': // GIF doesn't support true color.
|
case 'image/gif': // GIF doesn't support true color.
|
||||||
|
|
|
@ -31,6 +31,7 @@ final class PhabricatorApplicationMacro extends PhabricatorApplication {
|
||||||
'comment/(?P<id>[1-9]\d*)/' => 'PhabricatorMacroCommentController',
|
'comment/(?P<id>[1-9]\d*)/' => 'PhabricatorMacroCommentController',
|
||||||
'edit/(?P<id>[1-9]\d*)/' => 'PhabricatorMacroEditController',
|
'edit/(?P<id>[1-9]\d*)/' => 'PhabricatorMacroEditController',
|
||||||
'disable/(?P<id>[1-9]\d*)/' => 'PhabricatorMacroDisableController',
|
'disable/(?P<id>[1-9]\d*)/' => 'PhabricatorMacroDisableController',
|
||||||
|
'meme/' => 'PhabricatorMacroMemeController',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorMacroMemeController
|
||||||
|
extends PhabricatorMacroController {
|
||||||
|
|
||||||
|
public function processRequest() {
|
||||||
|
$request = $this->getRequest();
|
||||||
|
$macro_name = $request->getStr('macro');
|
||||||
|
$upper_text = $request->getStr('uppertext');
|
||||||
|
$lower_text = $request->getStr('lowertext');
|
||||||
|
$user = $request->getUser();
|
||||||
|
$macro = id(new PhabricatorFileImageMacro())
|
||||||
|
->loadOneWhere('name=%s', $macro_name);
|
||||||
|
if (!$macro) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
$file = id(new PhabricatorFile())->loadOneWhere(
|
||||||
|
'phid = %s',
|
||||||
|
$macro->getFilePHID());
|
||||||
|
|
||||||
|
$upper_text = strtoupper($upper_text);
|
||||||
|
$lower_text = strtoupper($lower_text);
|
||||||
|
$mixed_text = $upper_text.":".$lower_text;
|
||||||
|
$hash = "meme".hash("sha256", $mixed_text);
|
||||||
|
$xform = id(new PhabricatorTransformedFile())
|
||||||
|
->loadOneWhere('originalphid=%s and transform=%s',
|
||||||
|
$file->getPHID(), $hash);
|
||||||
|
|
||||||
|
if ($xform) {
|
||||||
|
$memefile = id(new PhabricatorFile())->loadOneWhere(
|
||||||
|
'phid = %s', $xform->getTransformedPHID());
|
||||||
|
return id(new AphrontRedirectResponse())->setURI($memefile->getBestURI());
|
||||||
|
}
|
||||||
|
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||||
|
$transformers = (new PhabricatorImageTransformer());
|
||||||
|
$newfile = $transformers
|
||||||
|
->executeMemeTransform($file, $upper_text, $lower_text);
|
||||||
|
$xfile = new PhabricatorTransformedFile();
|
||||||
|
$xfile->setOriginalPHID($file->getPHID());
|
||||||
|
$xfile->setTransformedPHID($newfile->getPHID());
|
||||||
|
$xfile->setTransform($hash);
|
||||||
|
$xfile->save();
|
||||||
|
return id(new AphrontRedirectResponse())->setURI($newfile->getBestURI());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue