From 75f6211233019702fb39a11dea5cf56783977db9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 May 2015 12:24:07 -0700 Subject: [PATCH] Convert "preview" image transforms to new pathway Summary: Ref T7707. Move the 220px (file uploads) and 100px (Pholio thumbgrid) previews over to the new stuff. Test Plan: Uploaded a bunch of images to remarkup and Pholio; they generated reasonable results in the web UI. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T7707 Differential Revision: https://secure.phabricator.com/D12814 --- .../files/PhabricatorImageTransformer.php | 70 ----------------- .../PhabricatorFileTransformController.php | 14 ---- .../PhabricatorEmbedFileRemarkupRule.php | 16 ++-- .../files/storage/PhabricatorFile.php | 8 -- .../PhabricatorFileImageTransform.php | 10 +++ .../PhabricatorFileThumbnailTransform.php | 71 +++++++++++++++--- .../transform/PhabricatorFileTransform.php | 14 ++++ .../pholio/view/PholioMockThumbGridView.php | 36 +++++---- .../icon/fatcow/thumbnails/default.p100.png | Bin 1720 -> 0 bytes .../icon/fatcow/thumbnails/image.p100.png | Bin 2167 -> 0 bytes .../image/icon/fatcow/thumbnails/pdf.p100.png | Bin 2363 -> 0 bytes .../image/icon/fatcow/thumbnails/zip.p100.png | Bin 2067 -> 0 bytes 12 files changed, 118 insertions(+), 121 deletions(-) delete mode 100644 webroot/rsrc/image/icon/fatcow/thumbnails/default.p100.png delete mode 100644 webroot/rsrc/image/icon/fatcow/thumbnails/image.p100.png delete mode 100644 webroot/rsrc/image/icon/fatcow/thumbnails/pdf.p100.png delete mode 100644 webroot/rsrc/image/icon/fatcow/thumbnails/zip.p100.png diff --git a/src/applications/files/PhabricatorImageTransformer.php b/src/applications/files/PhabricatorImageTransformer.php index 95cf1fac7a..0bbcdb9b31 100644 --- a/src/applications/files/PhabricatorImageTransformer.php +++ b/src/applications/files/PhabricatorImageTransformer.php @@ -51,20 +51,6 @@ final class PhabricatorImageTransformer { )); } - public function executePreviewTransform( - PhabricatorFile $file, - $size) { - - $image = $this->generatePreview($file, $size); - - return PhabricatorFile::newFromFileData( - $image, - array( - 'name' => 'preview-'.$file->getName(), - 'canCDN' => true, - )); - } - public function executeConpherenceTransform( PhabricatorFile $file, $top, @@ -188,37 +174,6 @@ final class PhabricatorImageTransformer { } - public static function getPreviewDimensions(PhabricatorFile $file, $size) { - $metadata = $file->getMetadata(); - $x = idx($metadata, PhabricatorFile::METADATA_IMAGE_WIDTH); - $y = idx($metadata, PhabricatorFile::METADATA_IMAGE_HEIGHT); - - if (!$x || !$y) { - $data = $file->loadFileData(); - $src = imagecreatefromstring($data); - - $x = imagesx($src); - $y = imagesy($src); - } - - $scale = min($size / $x, $size / $y, 1); - - $dx = max($size / 4, $scale * $x); - $dy = max($size / 4, $scale * $y); - - $sdx = $scale * $x; - $sdy = $scale * $y; - - return array( - 'x' => $x, - 'y' => $y, - 'dx' => $dx, - 'dy' => $dy, - 'sdx' => $sdx, - 'sdy' => $sdy, - ); - } - public static function getScaleForCrop( PhabricatorFile $file, $des_width, @@ -241,31 +196,6 @@ final class PhabricatorImageTransformer { return $scale; } - private function generatePreview(PhabricatorFile $file, $size) { - $data = $file->loadFileData(); - $src = imagecreatefromstring($data); - - $dimensions = self::getPreviewDimensions($file, $size); - $x = $dimensions['x']; - $y = $dimensions['y']; - $dx = $dimensions['dx']; - $dy = $dimensions['dy']; - $sdx = $dimensions['sdx']; - $sdy = $dimensions['sdy']; - - $dst = $this->getBlankDestinationFile($dx, $dy); - - imagecopyresampled( - $dst, - $src, - ($dx - $sdx) / 2, ($dy - $sdy) / 2, - 0, 0, - $sdx, $sdy, - $x, $y); - - return self::saveImageDataInAnyFormat($dst, $file->getMimeType()); - } - private function applyMemeToFile( PhabricatorFile $file, $upper_text, diff --git a/src/applications/files/controller/PhabricatorFileTransformController.php b/src/applications/files/controller/PhabricatorFileTransformController.php index dd7c355061..ae31990c06 100644 --- a/src/applications/files/controller/PhabricatorFileTransformController.php +++ b/src/applications/files/controller/PhabricatorFileTransformController.php @@ -85,12 +85,6 @@ final class PhabricatorFileTransformController case 'thumb-280x210': $xformed_file = $this->executeThumbTransform($file, 280, 210); break; - case 'preview-100': - $xformed_file = $this->executePreviewTransform($file, 100); - break; - case 'preview-220': - $xformed_file = $this->executePreviewTransform($file, 220); - break; default: return new Aphront400Response(); } @@ -132,9 +126,6 @@ final class PhabricatorFileTransformController case 'thumb-280x210': $suffix = '280x210'; break; - case 'preview-100': - $suffix = '.p100'; - break; default: throw new Exception('Unsupported transformation type!'); } @@ -163,11 +154,6 @@ final class PhabricatorFileTransformController return $file->getRedirectResponse(); } - private function executePreviewTransform(PhabricatorFile $file, $size) { - $xformer = new PhabricatorImageTransformer(); - return $xformer->executePreviewTransform($file, $size); - } - private function executeThumbTransform(PhabricatorFile $file, $x, $y) { $xformer = new PhabricatorImageTransformer(); return $xformer->executeThumbTransform($file, $x, $y); diff --git a/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php b/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php index 87d611c0ef..5ffb70be7f 100644 --- a/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php +++ b/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php @@ -107,11 +107,17 @@ final class PhabricatorEmbedFileRemarkupRule break; case 'thumb': default: - $attrs['src'] = $file->getPreview220URI(); - $dimensions = - PhabricatorImageTransformer::getPreviewDimensions($file, 220); - $attrs['width'] = $dimensions['dx']; - $attrs['height'] = $dimensions['dy']; + $preview_key = PhabricatorFileThumbnailTransform::TRANSFORM_PREVIEW; + $xform = PhabricatorFileTransform::getTransformByKey($preview_key); + $attrs['src'] = $file->getURIForTransform($xform); + + $dimensions = $xform->getTransformedDimensions($file); + if ($dimensions) { + list($x, $y) = $dimensions; + $attrs['width'] = $x; + $attrs['height'] = $y; + } + $image_class = 'phabricator-remarkup-embed-image'; break; } diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index c4aa7dc42c..9ff75cce17 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -788,14 +788,6 @@ final class PhabricatorFile extends PhabricatorFileDAO return $this->getTransformedURI('thumb-profile'); } - public function getPreview100URI() { - return $this->getTransformedURI('preview-100'); - } - - public function getPreview220URI() { - return $this->getTransformedURI('preview-220'); - } - public function getThumb280x210URI() { return $this->getTransformedURI('thumb-280x210'); } diff --git a/src/applications/files/transform/PhabricatorFileImageTransform.php b/src/applications/files/transform/PhabricatorFileImageTransform.php index 3c44825dd5..250aa887dd 100644 --- a/src/applications/files/transform/PhabricatorFileImageTransform.php +++ b/src/applications/files/transform/PhabricatorFileImageTransform.php @@ -8,6 +8,16 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform { private $imageX; private $imageY; + /** + * Get an estimate of the transformed dimensions of a file. + * + * @param PhabricatorFile File to transform. + * @return list|null Width and height, if available. + */ + public function getTransformedDimensions(PhabricatorFile $file) { + return null; + } + public function canApplyTransform(PhabricatorFile $file) { if (!$file->isViewableImage()) { return false; diff --git a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php index 45d7ad670a..3a72b5cf9e 100644 --- a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php +++ b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php @@ -65,6 +65,59 @@ final class PhabricatorFileThumbnailTransform $dst_x = $this->dstX; $dst_y = $this->dstY; + $dimensions = $this->computeDimensions( + $src_x, + $src_y, + $dst_x, + $dst_y); + + $copy_x = $dimensions['copy_x']; + $copy_y = $dimensions['copy_y']; + $use_x = $dimensions['use_x']; + $use_y = $dimensions['use_y']; + $dst_x = $dimensions['dst_x']; + $dst_y = $dimensions['dst_y']; + + return $this->applyCropAndScale( + $dst_x, + $dst_y, + ($src_x - $copy_x) / 2, + ($src_y - $copy_y) / 2, + $copy_x, + $copy_y, + $use_x, + $use_y); + } + + + public function getTransformedDimensions(PhabricatorFile $file) { + $dst_x = $this->dstX; + $dst_y = $this->dstY; + + // If this is transform has fixed dimensions, we can trivially predict + // the dimensions of the transformed file. + if ($dst_y !== null) { + return array($dst_x, $dst_y); + } + + $src_x = $file->getImageWidth(); + $src_y = $file->getImageHeight(); + + if (!$src_x || !$src_y) { + return null; + } + + $dimensions = $this->computeDimensions( + $src_x, + $src_y, + $dst_x, + $dst_y); + + return array($dimensions['dst_x'], $dimensions['dst_y']); + } + + + private function computeDimensions($src_x, $src_y, $dst_x, $dst_y) { if ($dst_y === null) { // If we only have one dimension, it represents a maximum dimension. // The other dimension of the transform is scaled appropriately, except @@ -115,17 +168,17 @@ final class PhabricatorFileThumbnailTransform $use_y = $dst_y; } - return $this->applyCropAndScale( - $dst_x, - $dst_y, - ($src_x - $copy_x) / 2, - ($src_y - $copy_y) / 2, - $copy_x, - $copy_y, - $use_x, - $use_y); + return array( + 'copy_x' => $copy_x, + 'copy_y' => $copy_y, + 'use_x' => $use_x, + 'use_y' => $use_y, + 'dst_x' => $dst_x, + 'dst_y' => $dst_y, + ); } + public function getDefaultTransform(PhabricatorFile $file) { $x = (int)$this->dstX; $y = (int)$this->dstY; diff --git a/src/applications/files/transform/PhabricatorFileTransform.php b/src/applications/files/transform/PhabricatorFileTransform.php index f12aefd3ed..633a80887a 100644 --- a/src/applications/files/transform/PhabricatorFileTransform.php +++ b/src/applications/files/transform/PhabricatorFileTransform.php @@ -45,4 +45,18 @@ abstract class PhabricatorFileTransform extends Phobject { return $map; } + public static function getTransformByKey($key) { + $all = self::getAllTransforms(); + + $xform = idx($all, $key); + if (!$xform) { + throw new Exception( + pht( + 'No file transform with key "%s" exists.', + $key)); + } + + return $xform; + } + } diff --git a/src/applications/pholio/view/PholioMockThumbGridView.php b/src/applications/pholio/view/PholioMockThumbGridView.php index df9fe1aa06..8e9d3007c5 100644 --- a/src/applications/pholio/view/PholioMockThumbGridView.php +++ b/src/applications/pholio/view/PholioMockThumbGridView.php @@ -114,28 +114,34 @@ final class PholioMockThumbGridView extends AphrontView { private function renderThumbnail(PholioImage $image) { $thumbfile = $image->getFile(); + $preview_key = PhabricatorFileThumbnailTransform::TRANSFORM_THUMBGRID; + $xform = PhabricatorFileTransform::getTransformByKey($preview_key); + + $attributes = array( + 'class' => 'pholio-mock-thumb-grid-image', + 'src' => $thumbfile->getURIForTransform($xform), + ); + if ($image->getFile()->isViewableImage()) { - $dimensions = PhabricatorImageTransformer::getPreviewDimensions( - $thumbfile, - 100); + $dimensions = $xform->getTransformedDimensions($thumbfile); + if ($dimensions) { + list($x, $y) = $dimensions; + $attributes += array( + 'width' => $x, + 'height' => $y, + 'style' => 'top: '.floor((100 - $y) / 2).'px', + ); + } } else { // If this is a PDF or a text file or something, we'll end up using a // generic thumbnail which is always sized correctly. - $dimensions = array( - 'sdx' => 100, - 'sdy' => 100, + $attributes += array( + 'width' => 100, + 'height' => 100, ); } - $tag = phutil_tag( - 'img', - array( - 'width' => $dimensions['sdx'], - 'height' => $dimensions['sdy'], - 'src' => $thumbfile->getPreview100URI(), - 'class' => 'pholio-mock-thumb-grid-image', - 'style' => 'top: '.floor((100 - $dimensions['sdy'] ) / 2).'px', - )); + $tag = phutil_tag('img', $attributes); $classes = array('pholio-mock-thumb-grid-item'); if ($image->getIsObsolete()) { diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/default.p100.png b/webroot/rsrc/image/icon/fatcow/thumbnails/default.p100.png deleted file mode 100644 index f713c2398bafd8dad1d22b179532f0c3920c7dd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1720 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 NV0-$rYCU4I{tE^`U)BHs diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/pdf.p100.png b/webroot/rsrc/image/icon/fatcow/thumbnails/pdf.p100.png deleted file mode 100644 index ad3a39b490701b073d84d8e84756782481356adf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2363 zcmaJ@X;f3!7LLp_5n;(BTmlLTAqgZ5fgl+TLL@>cgCbbRMa2(?z;C3`+VQG_c?p7v+noIRVysb z9L-=bm}LZ=5~J;Ty3fQ&+e-TOv9z57L`{HV#p@wCTMEL6JTV6ZB82QTFa~7vvNK!3 zP#8?#gU?EU5*Sf0(>!8TtEQE2k=8<{7`7^8-T=M@wfoIUm)<|L1?{6 zdF$~p6xs)0+8F_{0fHoW6iP0a`^x=&#nLnsCO9})r{U*^)FP0wY!Sp(AVo6wIRy$R z<4XAwh%XiaIz=`|oCOgO+DPATK`8ksE0TRUChdWt6l@6!{C}uW_z^9G zV!%)FeoX<+$l0Qh#|3TgIEHPV*>y$ z2Aj(l=?qKXF&GSdgh&RlMO-j~LO^IWeEEDHo*YOb1^8jfR5FQ(!BG8!f=FaC5sk)B z{Ly4w0CtW`5p%PIpa`1d@;-4XU*+nGK`7CBrhroZMvzC7iiN4rh=oYQ2Kp0e zw6E%Y;PSo@%R4R!js7YZr40t9OYLW=o-=9dM>qUfxZ26b(g#J_dY5Vo`|U*OY8cG$ zOaz69}jBY)#6`G#cZc+D89Jsyb^y;-AS$ zH+){r?%7kKz8_MeY6^aNNR~$Xv);0epb0WCAk8;XG7U(8trBiR1$5K*?PtbMOCOAL zcwFxI#X(L1{K%cDDXWIMwt64llyHAK@|fwoTUhI`l;mWjHAOQtSpIX%?dFTkC&xYD zx#f=b_Ge4iBz5<{n#gd{-_;&^$F*rq$Hd_{mpwNN&yO83+|XTTJe;1+j7xl#5gXb5 zTbAH%bd+%W zTTa(k`rQ9xu;QM2;A->bCMJtjFs{*@$TPN@0L^XV^;>WEjlZ(!pPn)Gir%}=E;72+ z%&Xe)#BjQ!&0wjoO+#^YF5F}q;~5x;$&1^&e_?yU-}ybq6>H_xnqy%z)zO2ix7Vx0 zHxbxpz2=z34)r{6}Ca{W+mKH#+T=A-`RiZ!Zzh(t<~vevQVRCi#GOX=y%Fhegd1MgG>DVDpdI=!rwy6a(tdQ z0=xe@P^+mXauSOZ&cTB+Z8#Y|W*+N;%=AjOE{)s;Ch-pr)k)@ccIn>>5GQRW2)N=)@fC#@ zyKxPCE!{Ml9cy#ZQ={LHq#qnpy>iS)i7qKx$>{j7t#*$dJvn+|+Nt;1jeY%_h&|mk z4?FH3Zp$|D=+f+U&IqxyNR}JTwmn9UHKkY&zw|eG`qsI~H9B0~wCErGALdU(6_2QW zjog;pXZjQ7heE=FyYJW?nLZQNB&i$uEwbA~sXl47`f_kR&t`O#cxn*#re`R1@(AtU zhpGx5Y!>dn?sEAq7}*(DQ#II?f5kKYpBE_(HEI#%C}2ac(u-DIl-yYMltnKxL3rk3 z8Y*3~kw-6GcwBO_qHY21SLoDpz>(}uDBR+{0-nb#3#Z(hc$vKL_W7li{BbIUpEm&C zG`jfVSf+w!(coecr993nI5Z)T>fU{h$iJ!~SqueA;XN*b;U_IJmkX;V;ilL8Ec(|y zxqj6(@4MK^Qsyt2-v64GIFVAa+Sr!=**0)@5{lj8l5O z3~DcOz|FSl7y`Z{+@t^j*E?=f5bmYt2Dh5`+4VMe1%CA+tgC7=w1>GrJ4hd5QPjHM NnF#7CN;N4p|9?z`!d(CW diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/zip.p100.png b/webroot/rsrc/image/icon/fatcow/thumbnails/zip.p100.png deleted file mode 100644 index 86fa739b3b5bcbbafabf59b4aa990df899d2f954..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2067 zcmaJ?c~lcu7awpbv>!@qL5kRpF)E5#Nuo&!TY?ZFDTYMhpkhcSFpbH?WPq?FSfp%H zsu*f*1xf*n3q{hZCn!?o6p^6f!Xm{}DisltPgyF^5BMf3=pUtXX5M@EzTfY6@4N4w zGufenK!>+o-v$7{ffvLTVzbQp*)7CI_5A`7Hhq8wgri}I98HjDAiz(CL_;7?DT#rE zkVKZa=PKj{0Q2y$C>#yvhp?oGk|?oah&rVjV*`Mfk4`O-Dj*b$hGJk98~5MybvO`~ zv2okzd@^6nfnwpHBn=dnBoIlH6jG)P=i?1}=~$S65<(@QPPtp9W$Dyc)FeEz>fmC9MP z78OFT^ZiF*tte3qk%W*IiPK22i<7UkhElUQ8c2d78WDo*o~dGJEP^81SVRqS!Wf`C zUm}H7R>#^G3_hR5Q)y9&N(%9~Y#gRRgkc$rv%%k=L8X%eIR1VVN`U9ajs6^tADK+y zdXhPG25pARMWk^`NQKUDWv{uEIl0zmP^z)WTu1}QL$Xa8LSIn^%Lnj2aX0S zmTYT&HaV76YRWsTG#X;9}*9&P(bT`M z7iLsPSN9d0(I*adJ8{J4Dn(=eDMND9h4n3#%O@9lTu9>oTKy&3-LgV+H!Nc)ah<`% zQ~JF}eZBFjtnX=_^6u@WRkg7T7cgZR#V1`u@NU`6ys((AE0g0>2KzlmDY;e459&EP z0;ko~)VMUYb@*0$AGgRX_~2!b`J%UW=hV;xpU+MwUd8Kok@^P*^Bj!fK=xVS7O>Cp ziH|N+z|f0V9U69jkVtnAj8|DSf@;^CQC~a_u5`^U{R-CFrqO>XvDjY;xy)#kn!dyg0fYv_vDzJBqEoolOBuX=W| zHW{wRB``lC%9Gy$rh{DEK0XY9r8iT4+Td~!qX-Clx3t$}Yhe9`c|$bMIXZR2`P z5GU28Xs9G}U@@CJu{t&72V{5apWga+JI;88je2|3pBZIxw{~sm4Q=V2NOQyOa(lGR zk2#Rhd*_3*g3i7brVq_jvhRo6gu>Fu}h)e+xr z{7sWl?l%49$u^91j97GcIs-fa z*soD@d9$L1Ip8$h_voh>dF6_8e{8%|JrWqTWP7IDE!)L#-=XwtMdXP;?U#QXZ)~fI zw2+H#*PeeKdt>k(?bjYtZ&qpa`8Zq&jG{(oVqaO$8o@VP^UCYS+i}A z(<~Y6Z#)S12Ck2W*GgzSE;rC=Uiv!4U-=I8jmzDO#x@@^5Q@H&_bP!~ zX$Qo^#)@nHtrxl%9JqV1qTp<0XL+SfwM*_9-;tkVmR3&575j_#hrd(SY##4z%%Xl7 z89C~ Nc>x0MkN&&T{tqesJH-G1