@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator

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

authored by

Debarghya Das and committed by
epriestley
fa19618d f919f000

+151
resources/font/tuffy.ttf

This is a binary file and will not be displayed.

+2
src/__phutil_library_map__.php
··· 925 925 'PhabricatorMacroEditController' => 'applications/macro/controller/PhabricatorMacroEditController.php', 926 926 'PhabricatorMacroEditor' => 'applications/macro/editor/PhabricatorMacroEditor.php', 927 927 'PhabricatorMacroListController' => 'applications/macro/controller/PhabricatorMacroListController.php', 928 + 'PhabricatorMacroMemeController' => 'applications/macro/controller/PhabricatorMacroMemeController.php', 928 929 'PhabricatorMacroReplyHandler' => 'applications/macro/mail/PhabricatorMacroReplyHandler.php', 929 930 'PhabricatorMacroTransaction' => 'applications/macro/storage/PhabricatorMacroTransaction.php', 930 931 'PhabricatorMacroTransactionComment' => 'applications/macro/storage/PhabricatorMacroTransactionComment.php', ··· 2308 2309 'PhabricatorMacroEditController' => 'PhabricatorMacroController', 2309 2310 'PhabricatorMacroEditor' => 'PhabricatorApplicationTransactionEditor', 2310 2311 'PhabricatorMacroListController' => 'PhabricatorMacroController', 2312 + 'PhabricatorMacroMemeController' => 'PhabricatorMacroController', 2311 2313 'PhabricatorMacroReplyHandler' => 'PhabricatorMailReplyHandler', 2312 2314 'PhabricatorMacroTransaction' => 'PhabricatorApplicationTransaction', 2313 2315 'PhabricatorMacroTransactionComment' => 'PhabricatorApplicationTransactionComment',
+103
src/applications/files/PhabricatorImageTransformer.php
··· 2 2 3 3 final class PhabricatorImageTransformer { 4 4 5 + public function executeMemeTransform( 6 + PhabricatorFile $file, 7 + $upper_text, 8 + $lower_text) { 9 + $image = $this->applyMemeTo($file, $upper_text, $lower_text); 10 + return PhabricatorFile::newFromFileData( 11 + $image, 12 + array( 13 + 'name' => 'meme-'.$file->getName(), 14 + )); 15 + } 16 + 5 17 public function executeThumbTransform( 6 18 PhabricatorFile $file, 7 19 $x, ··· 132 144 $x, $y); 133 145 134 146 return $this->saveImageDataInAnyFormat($dst, $file->getMimeType()); 147 + } 148 + 149 + private function applyMemeTo( 150 + PhabricatorFile $file, 151 + $upper_text, 152 + $lower_text) { 153 + $data = $file->loadFileData(); 154 + $img = imagecreatefromstring($data); 155 + $phabricator_root = dirname(phutil_get_library_root('phabricator')); 156 + $font_path = $phabricator_root.'/resources/font/tuffy.ttf'; 157 + $white = imagecolorallocate($img, 255, 255, 255); 158 + $black = imagecolorallocate($img, 0, 0, 0); 159 + $border_width = 3; 160 + $font_max = 200; 161 + $font_min = 5; 162 + for ($i = $font_max; $i > $font_min; $i--) { 163 + $fit = $this->doesTextBoundingBoxFitInImage( 164 + $img, 165 + $upper_text, 166 + $i, 167 + $font_path); 168 + if ($fit['doesfit']) { 169 + $x = ($fit['imgwidth'] - $fit['txtwidth']) / 2; 170 + $y = $fit['txtheight'] + 10; 171 + $this->makeImageWithTextBorder($img, 172 + $i, 173 + $x, 174 + $y, 175 + $white, 176 + $black, 177 + $border_width, 178 + $font_path, 179 + $upper_text); 180 + break; 181 + } 182 + } 183 + for ($i = $font_max; $i > $font_min; $i--) { 184 + $fit = $this->doesTextBoundingBoxFitInImage($img, 185 + $lower_text, $i, $font_path); 186 + if ($fit['doesfit']) { 187 + $x = ($fit['imgwidth'] - $fit['txtwidth']) / 2; 188 + $y = $fit['imgheight'] - 10; 189 + $this->makeImageWithTextBorder( 190 + $img, 191 + $i, 192 + $x, 193 + $y, 194 + $white, 195 + $black, 196 + $border_width, 197 + $font_path, 198 + $lower_text); 199 + break; 200 + } 201 + } 202 + return $this->saveImageDataInAnyFormat($img, $file->getMimeType()); 203 + } 204 + 205 + private function makeImageWithTextBorder($img, $font_size, $x, $y, 206 + $color, $stroke_color, $bw, $font, $text) { 207 + $angle = 0; 208 + $bw = abs($bw); 209 + for ($c1 = $x - $bw; $c1 <= $x + $bw; $c1++) { 210 + for ($c2 = $y - $bw; $c2 <= $y + $bw; $c2++) { 211 + if (!(($c1 == $x - $bw || $x + $bw) && 212 + $c2 == $y - $bw || $c2 == $y + $bw)) { 213 + $bg = imagettftext($img, $font_size, 214 + $angle, $c1, $c2, $stroke_color, $font, $text); 215 + } 216 + } 217 + } 218 + imagettftext($img, $font_size, $angle, 219 + $x , $y, $color , $font, $text); 220 + } 221 + 222 + private function doesTextBoundingBoxFitInImage($img, 223 + $text, $font_size, $font_path) { 224 + // Default Angle = 0 225 + $angle = 0; 226 + 227 + $bbox = imagettfbbox($font_size, $angle, $font_path, $text); 228 + $text_height = abs($bbox[3] - $bbox[5]); 229 + $text_width = abs($bbox[0] - $bbox[2]); 230 + return array( 231 + "doesfit" => ($text_height * 1.05 <= imagesy($img) 232 + && $text_width * 1.05 <= imagesx($img)), 233 + "txtwidth" => $text_width, 234 + "txtheight" => $text_height, 235 + "imgwidth" => imagesx($img), 236 + "imgheight" => imagesy($img), 237 + ); 135 238 } 136 239 137 240 private function saveImageDataInAnyFormat($data, $preferred_mime = '') {
+1
src/applications/macro/application/PhabricatorApplicationMacro.php
··· 31 31 'comment/(?P<id>[1-9]\d*)/' => 'PhabricatorMacroCommentController', 32 32 'edit/(?P<id>[1-9]\d*)/' => 'PhabricatorMacroEditController', 33 33 'disable/(?P<id>[1-9]\d*)/' => 'PhabricatorMacroDisableController', 34 + 'meme/' => 'PhabricatorMacroMemeController', 34 35 ), 35 36 ); 36 37 }
+45
src/applications/macro/controller/PhabricatorMacroMemeController.php
··· 1 + <?php 2 + 3 + final class PhabricatorMacroMemeController 4 + extends PhabricatorMacroController { 5 + 6 + public function processRequest() { 7 + $request = $this->getRequest(); 8 + $macro_name = $request->getStr('macro'); 9 + $upper_text = $request->getStr('uppertext'); 10 + $lower_text = $request->getStr('lowertext'); 11 + $user = $request->getUser(); 12 + $macro = id(new PhabricatorFileImageMacro()) 13 + ->loadOneWhere('name=%s', $macro_name); 14 + if (!$macro) { 15 + return new Aphront404Response(); 16 + } 17 + $file = id(new PhabricatorFile())->loadOneWhere( 18 + 'phid = %s', 19 + $macro->getFilePHID()); 20 + 21 + $upper_text = strtoupper($upper_text); 22 + $lower_text = strtoupper($lower_text); 23 + $mixed_text = $upper_text.":".$lower_text; 24 + $hash = "meme".hash("sha256", $mixed_text); 25 + $xform = id(new PhabricatorTransformedFile()) 26 + ->loadOneWhere('originalphid=%s and transform=%s', 27 + $file->getPHID(), $hash); 28 + 29 + if ($xform) { 30 + $memefile = id(new PhabricatorFile())->loadOneWhere( 31 + 'phid = %s', $xform->getTransformedPHID()); 32 + return id(new AphrontRedirectResponse())->setURI($memefile->getBestURI()); 33 + } 34 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 35 + $transformers = (new PhabricatorImageTransformer()); 36 + $newfile = $transformers 37 + ->executeMemeTransform($file, $upper_text, $lower_text); 38 + $xfile = new PhabricatorTransformedFile(); 39 + $xfile->setOriginalPHID($file->getPHID()); 40 + $xfile->setTransformedPHID($newfile->getPHID()); 41 + $xfile->setTransform($hash); 42 + $xfile->save(); 43 + return id(new AphrontRedirectResponse())->setURI($newfile->getBestURI()); 44 + } 45 + }