diff --git a/src/applications/auth/view/PhabricatorAuthAccountView.php b/src/applications/auth/view/PhabricatorAuthAccountView.php index a68cdfff07..13574a9b6a 100644 --- a/src/applications/auth/view/PhabricatorAuthAccountView.php +++ b/src/applications/auth/view/PhabricatorAuthAccountView.php @@ -89,13 +89,17 @@ final class PhabricatorAuthAccountView extends AphrontView { $account_uri); } - $image_uri = $account->getProfileImageFile()->getProfileThumbURI(); + $image_file = $account->getProfileImageFile(); + $xform = PhabricatorFileTransform::getTransformByKey( + PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE); + $image_uri = $image_file->getURIForTransform($xform); + list($x, $y) = $xform->getTransformedDimensions($image_file); return phutil_tag( 'div', array( 'class' => 'auth-account-view', - 'style' => 'background-image: url('.$image_uri.')', + 'style' => 'background-image: url('.$image_uri.');', ), $content); } diff --git a/src/applications/files/PhabricatorImageTransformer.php b/src/applications/files/PhabricatorImageTransformer.php index 0bbcdb9b31..34cdf67023 100644 --- a/src/applications/files/PhabricatorImageTransformer.php +++ b/src/applications/files/PhabricatorImageTransformer.php @@ -20,21 +20,6 @@ final class PhabricatorImageTransformer { )); } - public function executeThumbTransform( - PhabricatorFile $file, - $x, - $y) { - - $image = $this->crudelyScaleTo($file, $x, $y); - - return PhabricatorFile::newFromFileData( - $image, - array( - 'name' => 'thumb-'.$file->getName(), - 'canCDN' => true, - )); - } - public function executeProfileTransform( PhabricatorFile $file, $x, @@ -123,21 +108,6 @@ final class PhabricatorImageTransformer { return self::saveImageDataInAnyFormat($dst, $file->getMimeType()); } - - /** - * Very crudely scale an image up or down to an exact size. - */ - private function crudelyScaleTo(PhabricatorFile $file, $dx, $dy) { - $scaled = $this->applyScaleWithImagemagick($file, $dx, $dy); - - if ($scaled != null) { - return $scaled; - } - - $dst = $this->applyScaleTo($file, $dx, $dy); - return self::saveImageDataInAnyFormat($dst, $file->getMimeType()); - } - private function getBlankDestinationFile($dx, $dy) { $dst = imagecreatetruecolor($dx, $dy); imagesavealpha($dst, true); diff --git a/src/applications/files/controller/PhabricatorFileTransformController.php b/src/applications/files/controller/PhabricatorFileTransformController.php index ae31990c06..5062fe76fc 100644 --- a/src/applications/files/controller/PhabricatorFileTransformController.php +++ b/src/applications/files/controller/PhabricatorFileTransformController.php @@ -44,50 +44,33 @@ final class PhabricatorFileTransformController } } - $type = $file->getMimeType(); - - if (!$file->isViewableInBrowser() || !$file->isTransformableImage()) { - return $this->buildDefaultTransformation($file, $transform); + $xforms = PhabricatorFileTransform::getAllTransforms(); + if (!isset($xforms[$transform])) { + return new Aphront404Response(); } + $xform = $xforms[$transform]; + // We're essentially just building a cache here and don't need CSRF // protection. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $xformed_file = null; - - $xforms = PhabricatorFileTransform::getAllTransforms(); - if (isset($xforms[$transform])) { - $xform = $xforms[$transform]; - if ($xform->canApplyTransform($file)) { - try { - $xformed_file = $xforms[$transform]->applyTransform($file); - } catch (Exception $ex) { - // In normal transform mode, we ignore failures and generate a - // default transform below. If we're explicitly regenerating the - // thumbnail, rethrow the exception. - if ($is_regenerate) { - throw $ex; - } + if ($xform->canApplyTransform($file)) { + try { + $xformed_file = $xforms[$transform]->applyTransform($file); + } catch (Exception $ex) { + // In normal transform mode, we ignore failures and generate a + // default transform below. If we're explicitly regenerating the + // thumbnail, rethrow the exception. + if ($is_regenerate) { + throw $ex; } } - - if (!$xformed_file) { - $xformed_file = $xform->getDefaultTransform($file); - } } if (!$xformed_file) { - switch ($transform) { - case 'thumb-profile': - $xformed_file = $this->executeThumbTransform($file, 50, 50); - break; - case 'thumb-280x210': - $xformed_file = $this->executeThumbTransform($file, 280, 210); - break; - default: - return new Aphront400Response(); - } + $xformed_file = $xform->getDefaultTransform($file); } if (!$xformed_file) { @@ -103,40 +86,6 @@ final class PhabricatorFileTransformController return $this->buildTransformedFileResponse($xform); } - private function buildDefaultTransformation( - PhabricatorFile $file, - $transform) { - static $regexps = array( - '@application/zip@' => 'zip', - '@image/@' => 'image', - '@application/pdf@' => 'pdf', - '@.*@' => 'default', - ); - - $type = $file->getMimeType(); - $prefix = 'default'; - foreach ($regexps as $regexp => $implied_prefix) { - if (preg_match($regexp, $type)) { - $prefix = $implied_prefix; - break; - } - } - - switch ($transform) { - case 'thumb-280x210': - $suffix = '280x210'; - break; - default: - throw new Exception('Unsupported transformation type!'); - } - - $path = celerity_get_resource_uri( - "rsrc/image/icon/fatcow/thumbnails/{$prefix}{$suffix}.png"); - - return id(new AphrontRedirectResponse()) - ->setURI($path); - } - private function buildTransformedFileResponse( PhabricatorTransformedFile $xform) { @@ -154,11 +103,6 @@ final class PhabricatorFileTransformController return $file->getRedirectResponse(); } - private function executeThumbTransform(PhabricatorFile $file, $x, $y) { - $xformer = new PhabricatorImageTransformer(); - return $xformer->executeThumbTransform($file, $x, $y); - } - private function destroyTransform(PhabricatorTransformedFile $xform) { $file = id(new PhabricatorFileQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 9ff75cce17..3de1bdc9f6 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -784,14 +784,6 @@ final class PhabricatorFile extends PhabricatorFileDAO return PhabricatorEnv::getCDNURI($path); } - public function getProfileThumbURI() { - return $this->getTransformedURI('thumb-profile'); - } - - public function getThumb280x210URI() { - return $this->getTransformedURI('thumb-280x210'); - } - public function isViewableInBrowser() { return ($this->getViewableMimeType() !== null); } diff --git a/src/applications/files/transform/PhabricatorFileImageTransform.php b/src/applications/files/transform/PhabricatorFileImageTransform.php index 250aa887dd..b6e6844653 100644 --- a/src/applications/files/transform/PhabricatorFileImageTransform.php +++ b/src/applications/files/transform/PhabricatorFileImageTransform.php @@ -42,13 +42,20 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform { $dst_w, $dst_h, $src_x, $src_y, $src_w, $src_h, - $use_w, $use_h) { + $use_w, $use_h, + $scale_up) { + + // Figure out the effective destination width, height, and offsets. + $cpy_w = min($dst_w, $use_w); + $cpy_h = min($dst_h, $use_h); + + // If we aren't scaling up, and are copying a very small source image, + // we're just going to center it in the destination image. + if (!$scale_up) { + $cpy_w = min($cpy_w, $src_w); + $cpy_h = min($cpy_h, $src_h); + } - // Figure out the effective destination width, height, and offsets. We - // never want to scale images up, so if we're copying a very small source - // image we're just going to center it in the destination image. - $cpy_w = min($dst_w, $src_w, $use_w); - $cpy_h = min($dst_h, $src_h, $use_h); $off_x = ($dst_w - $cpy_w) / 2; $off_y = ($dst_h - $cpy_h) / 2; @@ -58,11 +65,18 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform { $argv[] = '-shave'; $argv[] = $src_x.'x'.$src_y; $argv[] = '-resize'; - $argv[] = $dst_w.'x'.$dst_h.'>'; + + if ($scale_up) { + $argv[] = $dst_w.'x'.$dst_h; + } else { + $argv[] = $dst_w.'x'.$dst_h.'>'; + } + $argv[] = '-bordercolor'; $argv[] = 'rgba(255, 255, 255, 0)'; $argv[] = '-border'; $argv[] = $off_x.'x'.$off_y; + return $this->applyImagemagick($argv); } @@ -117,7 +131,13 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform { * @param string Raw file data. */ protected function newFileFromData($data) { - $name = $this->getTransformKey().'-'.$this->file->getName(); + if ($this->file) { + $name = $this->file->getName(); + } else { + $name = 'default.png'; + } + + $name = $this->getTransformKey().'-'.$name; return PhabricatorFile::newFromFileData( $data, diff --git a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php index 3a72b5cf9e..328742f2e8 100644 --- a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php +++ b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php @@ -12,6 +12,7 @@ final class PhabricatorFileThumbnailTransform private $key; private $dstX; private $dstY; + private $scaleUp; public function setName($name) { $this->name = $name; @@ -29,6 +30,11 @@ final class PhabricatorFileThumbnailTransform return $this; } + public function setScaleUp($scale) { + $this->scaleUp = $scale; + return $this; + } + public function getTransformName() { return $this->name; } @@ -42,7 +48,8 @@ final class PhabricatorFileThumbnailTransform id(new PhabricatorFileThumbnailTransform()) ->setName(pht("Profile (100px \xC3\x97 100px)")) ->setKey(self::TRANSFORM_PROFILE) - ->setDimensions(100, 100), + ->setDimensions(100, 100) + ->setScaleUp(true), id(new PhabricatorFileThumbnailTransform()) ->setName(pht("Pinboard (280px \xC3\x97 210px)")) ->setKey(self::TRANSFORM_PINBOARD) @@ -86,7 +93,8 @@ final class PhabricatorFileThumbnailTransform $copy_x, $copy_y, $use_x, - $use_y); + $use_y, + $this->scaleUp); } @@ -144,22 +152,35 @@ final class PhabricatorFileThumbnailTransform $copy_x = $src_x; $copy_y = $src_y; } else { + $scale_up = $this->scaleUp; + // Otherwise, both dimensions are fixed. Figure out how much we'd have to // scale the image down along each dimension to get the entire thing to // fit. - $scale_x = min(($dst_x / $src_x), 1); - $scale_y = min(($dst_y / $src_y), 1); + $scale_x = ($dst_x / $src_x); + $scale_y = ($dst_y / $src_y); + + if (!$scale_up) { + $scale_x = min($scale_x, 1); + $scale_y = min($scale_y, 1); + } if ($scale_x > $scale_y) { // This image is relatively tall and narrow. We're going to crop off the // top and bottom. - $copy_x = $src_x; - $copy_y = min($src_y, $dst_y / $scale_x); + $scale = $scale_x; } else { // This image is relatively short and wide. We're going to crop off the // left and right. - $copy_x = min($src_x, $dst_x / $scale_y); - $copy_y = $src_y; + $scale = $scale_y; + } + + $copy_x = $dst_x / $scale_x; + $copy_y = $dst_y / $scale_x; + + if (!$scale_up) { + $copy_x = min($src_x, $copy_x); + $copy_y = min($src_y, $copy_y); } // In this mode, we always use the entire destination image. We may diff --git a/src/applications/macro/query/PhabricatorMacroSearchEngine.php b/src/applications/macro/query/PhabricatorMacroSearchEngine.php index 8a810c886f..8d632e818f 100644 --- a/src/applications/macro/query/PhabricatorMacroSearchEngine.php +++ b/src/applications/macro/query/PhabricatorMacroSearchEngine.php @@ -179,14 +179,18 @@ final class PhabricatorMacroSearchEngine assert_instances_of($macros, 'PhabricatorFileImageMacro'); $viewer = $this->requireViewer(); + $xform = PhabricatorFileTransform::getTransformByKey( + PhabricatorFileThumbnailTransform::TRANSFORM_PINBOARD); + $pinboard = new PHUIPinboardView(); foreach ($macros as $macro) { $file = $macro->getFile(); $item = new PHUIPinboardItemView(); if ($file) { - $item->setImageURI($file->getThumb280x210URI()); - $item->setImageSize(280, 210); + $item->setImageURI($file->getURIForTransform($xform)); + list($x, $y) = $xform->getTransformedDimensions($file); + $item->setImageSize($x, $y); } if ($macro->getDateCreated()) { diff --git a/src/applications/pholio/query/PholioMockSearchEngine.php b/src/applications/pholio/query/PholioMockSearchEngine.php index 226f225e11..057a370649 100644 --- a/src/applications/pholio/query/PholioMockSearchEngine.php +++ b/src/applications/pholio/query/PholioMockSearchEngine.php @@ -141,15 +141,22 @@ final class PholioMockSearchEngine extends PhabricatorApplicationSearchEngine { $viewer = $this->requireViewer(); + $xform = PhabricatorFileTransform::getTransformByKey( + PhabricatorFileThumbnailTransform::TRANSFORM_PINBOARD); + $board = new PHUIPinboardView(); foreach ($mocks as $mock) { + $image = $mock->getCoverFile(); + $image_uri = $image->getURIForTransform($xform); + list($x, $y) = $xform->getTransformedDimensions($image); + $header = 'M'.$mock->getID().' '.$mock->getName(); $item = id(new PHUIPinboardItemView()) ->setHeader($header) ->setURI('/M'.$mock->getID()) - ->setImageURI($mock->getCoverFile()->getThumb280x210URI()) - ->setImageSize(280, 210) + ->setImageURI($image_uri) + ->setImageSize($x, $y) ->setDisabled($mock->isClosed()) ->addIconCount('fa-picture-o', count($mock->getImages())) ->addIconCount('fa-trophy', $mock->getTokenCount()); diff --git a/src/applications/pholio/view/PholioMockEmbedView.php b/src/applications/pholio/view/PholioMockEmbedView.php index 81dfa670a3..b20283d48b 100644 --- a/src/applications/pholio/view/PholioMockEmbedView.php +++ b/src/applications/pholio/view/PholioMockEmbedView.php @@ -28,25 +28,29 @@ final class PholioMockEmbedView extends AphrontView { $this->mock->getImages(), array_flip($this->images)); } + $xform = PhabricatorFileTransform::getTransformByKey( + PhabricatorFileThumbnailTransform::TRANSFORM_PINBOARD); + if ($images_to_show) { - foreach ($images_to_show as $image) { - $thumbfile = $image->getFile(); - $thumbnail = $thumbfile->getThumb280x210URI(); - } + $image = head($images_to_show); + $thumbfile = $image->getFile(); $header = 'M'.$mock->getID().' '.$mock->getName(). ' (#'.$image->getID().')'; $uri = '/M'.$this->mock->getID().'/'.$image->getID().'/'; } else { - $thumbnail = $mock->getCoverFile()->getThumb280x210URI(); + $thumbfile = $mock->getCoverFile(); $header = 'M'.$mock->getID().' '.$mock->getName(); $uri = '/M'.$this->mock->getID(); } + $thumbnail = $thumbfile->getURIForTransform($xform); + list($x, $y) = $xform->getTransformedDimensions($thumbfile); + $item = id(new PHUIPinboardItemView()) ->setHeader($header) ->setURI($uri) ->setImageURI($thumbnail) - ->setImageSize(280, 210) + ->setImageSize($x, $y) ->setDisabled($mock->isClosed()) ->addIconCount('fa-picture-o', count($mock->getImages())) ->addIconCount('fa-trophy', $mock->getTokenCount()); diff --git a/src/applications/pholio/view/PholioMockImagesView.php b/src/applications/pholio/view/PholioMockImagesView.php index f894c698f2..d59e701579 100644 --- a/src/applications/pholio/view/PholioMockImagesView.php +++ b/src/applications/pholio/view/PholioMockImagesView.php @@ -68,8 +68,11 @@ final class PholioMockImagesView extends AphrontView { // TODO: We could maybe do a better job with tailoring this, which is the // image shown on the review stage. - $nonimage_uri = celerity_get_resource_uri( - 'rsrc/image/icon/fatcow/thumbnails/default.p100.png'); + $default_name = 'image-100x100.png'; + $builtins = PhabricatorFile::loadBuiltins( + $this->getUser(), + array($default_name)); + $default = $builtins[$default_name]; $engine = id(new PhabricatorMarkupEngine()) ->setViewer($this->getUser()); @@ -97,7 +100,7 @@ final class PholioMockImagesView extends AphrontView { 'fullURI' => $file->getBestURI(), 'stageURI' => ($file->isViewableImage() ? $file->getBestURI() - : $nonimage_uri), + : $default->getBestURI()), 'pageURI' => $this->getImagePageURI($image, $mock), 'downloadURI' => $file->getDownloadURI(), 'historyURI' => $history_uri, diff --git a/src/applications/pholio/view/PholioUploadedImageView.php b/src/applications/pholio/view/PholioUploadedImageView.php index fb8a82431e..2ff3ba0390 100644 --- a/src/applications/pholio/view/PholioUploadedImageView.php +++ b/src/applications/pholio/view/PholioUploadedImageView.php @@ -38,11 +38,15 @@ final class PholioUploadedImageView extends AphrontView { ->setSigil('image-description') ->setLabel(pht('Description')); + $xform = PhabricatorFileTransform::getTransformByKey( + PhabricatorFileThumbnailTransform::TRANSFORM_PINBOARD); + $thumbnail_uri = $file->getURIForTransform($xform); + $thumb_frame = phutil_tag( 'div', array( 'class' => 'pholio-thumb-frame', - 'style' => 'background-image: url('.$file->getThumb280x210URI().');', + 'style' => 'background-image: url('.$thumbnail_uri.');', )); $handle = javelin_tag( diff --git a/webroot/rsrc/css/application/auth/auth.css b/webroot/rsrc/css/application/auth/auth.css index 219c8795a3..8211eb1484 100644 --- a/webroot/rsrc/css/application/auth/auth.css +++ b/webroot/rsrc/css/application/auth/auth.css @@ -28,6 +28,7 @@ border: 1px solid {$lightblueborder}; background-repeat: no-repeat; background-position: 4px 4px; + background-size: 50px 50px; padding: 4px 4px 4px 62px; min-height: 50px; border-radius: 2px; diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/default280x210.png b/webroot/rsrc/image/icon/fatcow/thumbnails/default280x210.png deleted file mode 100644 index 7288c81954ec6a5e5916cdbbe6c93c2eb660390e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2123 zcmeAS@N?(olHy`uVBq!ia0y~yV3c5BV7SD=#=yWZvFrXT1_lPn64!{5;QX|b^2DN4 z2FH~Aq*MjZ+{EB@w}FfdWj%4dKI|^3?N`*Ur~^l zoSj;tkd&I9nP;o?e)oPQh0GLNrEpVU1K$GY)Qn7zs-o23D!-8As_bOT6eW8*1)B=1 zirj+S)RIJnirk#MVyg;UC9t_xdBs*BVSOb9u#%E&TP292B76fBob!uP6-@Pvb(0MY z4HeAH^bAc*&CCpS6pRcEjr0wS^bO2(4Na^}&8&>f6`(-LPQj)qCCw_x#SLm#QA(Pv zQbtKhft9{~d3m{Bxv^e;QM$gNrKKgv3?n1mqLeh<;>x^|#0uTKVr7^KE~&-IMVSR9 znfZANAQKal@=Hr>m6V{yR^%4Ijm;~D22*l=u6{*gfxe-h0mw@bzvdSB`r=obn+sQ6 zToROua4%Mai$f|4QuTvUi}Op1l2e0H)09DODzI`cN=+=uFAB-e&w+(vKt_H^esM;A zfr4|enSzFIVsd64NI(;;!`Ii!Gq1QLF)umQ)5TV)BDX* z(9qS`!otbf+0nqj(8bum+0@Jgrq?AuximL5uLPzy1)-xZPre(>$o&6x?nx#;I4|PQgYW6tzfE4HF7-aRV{IsS=bz?GzA6KQ%AK zR;ftI-fr^)mhTJE*JvTU6UOF+ch;0&#-B`@{Mm58$ z^5*Ko+=!D+t%~bPUWzB=C+n&l>#bwGIjcc2k=gK*nl8($Gf(I2G~R0D`(jDC*b_Vd z;>#k-wx4`?R{y_oes5|dTSKIV4x@rW!vO{k9`Y#$CT5U!il~MIVC@u9U?1c!+NPSZ zP9nMb|J3>6^S(WP`L%B5zom12$*fgs%ae$y{%}_P`Q_>V^GauL($U>^;DWhRLpZK%y&)Yj;-<#+#mbRRAvtpcf{5!r^VD-vpTklU%pXB&!PeDn2 zNBlpDQ}55WEpJu4xw=?+tdJzjvX965-*35;GD%)=*0U9U0n6S73G%(I z)Rwt-+57g=tseq!>Batj^yJZ>r&FgssN3iE`SkK#0+(DSy?@Yqdq-~HrHY21Uw%v! z(Elf5w$JeY^8|xEkIVPIsF}0x)zZhEqGA%W_PA4{dz7aq7PW z3;DC0JqIp)IdJ49Q}B}8_o8d%-q}^!&51K(n{jd8>%)gXKYkm&Wfzn4WAzzHJKoM* z$5!}g=l*$HyAK#tDsD;Aee9N*;FxeDt2@L$!6BjItCPm6w|=sXhKCgle0&U;nhyl5 zzc_DM=$UtSIC#_~x*HA{EaqO=v6WXQsP%w>;Xw`_ro%aUEn$JSp6lAGD;K{NV>+C6 zS7ye8KoyoYtKO^+ujkkE#>a=WJI-o3S62{_DIih1lIQ;8)GtlPWql1a^=j7_xg`9k z&p0MuI){^`?d)Xte;Va>0!lyvteql?g$=Bo5(?~t3gx{b2@EPqN4>T7ZVmud@1CxHF6*2UngD#s`Bwk{ diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/image280x210.png b/webroot/rsrc/image/icon/fatcow/thumbnails/image280x210.png deleted file mode 100644 index efdf733f8eeb118cb36403c6cb9a741d0ded879f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2600 zcmeAS@N?(olHy`uVBq!ia0y~yV3c5BV7SD=#=yWZvFrXT1_lPn64!{5;QX|b^2DN4 z2FH~Aq*MjZ+{EB@w}FfdWj%4dKI|^3?N`*Ur~^l zoSj;tkd&I9nP;o?e)oPQh0GLNrEpVU1K$GY)Qn7zs-o23D!-8As_bOT6eW8*1)B=1 zirj+S)RIJnirk#MVyg;UC9t_xdBs*BVSOb9u#%E&TP292B76fBob!uP6-@Pvb(0MY z4HeAH^bAc*&CCpS6pRcEjr0wS^bO2(4Na^}&8&>f6`(-LPQj)qCCw_x#SLm#QA(Pv zQbtKhft9{~d3m{Bxv^e;QM$gNrKKgv3?n1mqLeh<;>x^|#0uTKVr7^KE~&-IMVSR9 znfZANAQKal@=Hr>m6V{yR^%4Ijm;~D22*l=u6{*gfxe-h0mw@bzvdSB`r=obn+sQ6 zToROua4%Mai$f|4QuTvUi}Op1l2e0H)09DODzI`cN=+=uFAB-e&w+(vKt_H^esM;A zfr4|enSzFIVsd64NI(;;!`Ii!Gq1QLF)umQ)5TV)BDX*A zTTyg?o*-7OHkDLCEY0JuQU@MxU$wm2NNtg1 z%!(C?4}w@6?{eu~?T{8<=MotHkGW1KMq@{@=8iyft)^~m3G-b$#PwL-Dg9_%q_Dz~ zSK{-+5qB=)jxF!R($(~JGe{ljx_vmJ*-%fr zC{(KE&o+}#$-XW1uWo$4GKD>VU)A&XE9Vq8CU%HB&N{Yc$qt!)-(H>AymU{%rWf+k zPmkYz_iYl_#rn(Q_vh&xcTJG^El{l}j1 z{|+j>{b>i!Z10AMm7h$OX@ws?{MkLZ_Fv$v$1#!H_WZx_W@3i>PsxRPQv0{}uCj~B zEsyC*Flf@$)+y~eaq#9w6PC#PhH*2GR2A#IVtzRt9_Q2>vb<)*(%7xbl8Za zR^!-C&Cjmp{;Ph4&MKOsw5;`+4U z14Ukw=lfbk-=C*cY?u4F&N}|x>?OyK{WZy$<8`_2cZtdKdwpqDO1IX()BPB!C$?VV zpn`#m`3sF~?aIE*`n?wa3eRd6mX!n_x%NHv^zWbh{@mEnf4V}((E6{N&aJHxCfSDf z|Jba$`8<_nOOMZO`R#>Wz7r*WxCWFqo>ZPud8_{Sh1DBhEH_`gt$Nn3yxpd=_RRhH zsnulit?A30Lx1kvHuK1}mDiL09!po+FR9?)duzjq?gKn=k;X5r0^Kw!f2Q7vs}PKf zJpJ~1Oi_%V%zpbc>EoCGHRjc-)w^4F$@}jUyWH#l=vVZ;o?S2druVNlWNH@lku02Y z;7IF^KWZ*78w0t{_}l-{cALC9$mN&TSATiavlh#i|BJj=E#hXt!E;*Bi_e|YX}{9< z{Y;bh8?IlelcYQAXIO}KZQ-V)O1{0@E*W0iwL<55f}5LxbNa;c2e;zg4N4=I?0s>~ z-naJk-1$c{z82*8ojAU?_wyXFbu=D?kX7!|x#k16M%k8aS^@r;(kMY}jz~Di*LM$u$ zzCC(&wiVuWzIR=jg;OtX({f#P{oB!+&AV&t{=M)pnH~LN<}KjGqW zo_^mx>A>KKcZ~8^yZGEeHv4@OI?}g>XZ<%ZnDY7pP$pD=3JS+Eu(0ox%IU2 zH1nqwznTsNI2p{%KQgoUere;+?xKWGA6eKMB{w`x%KP_wr@mcX)%E|+zAvgb&3~P# z(wKN-+w2=({Y6*sB(faSl=yHZIAa`RG#K+`r`&j_(cJPp%c+(dgH|O8fHJhg)QO_SX6* z{S%oIocY@-HruYkH$lQ9;n>TxdbtyCKCV9{ek1IbS*_XD0}OAZR_8YyU|?caFra|q t-~nr=gkoS~25F~=0{h^P?q8(?42pu43iA$@JO{PiJYD@<);T3K0RTdE>%IU0 diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/pdf280x210.png b/webroot/rsrc/image/icon/fatcow/thumbnails/pdf280x210.png deleted file mode 100644 index 8036981aca18575e4fed1ebfa343188e5fed852c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2824 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 Iol`;+0Au=#;{X5v diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/zip280x210.png b/webroot/rsrc/image/icon/fatcow/thumbnails/zip280x210.png deleted file mode 100644 index 8db127b282c1ea95127548ba0022eabd8475315a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2511 zcmeAS@N?(olHy`uVBq!ia0y~yV3c5BV7SD=#=yWZvFrXT1_lPn64!{5;QX|b^2DN4 z2FH~Aq*MjZ+{EB@w}FfdWj%4dKI|^3?N`*Ur~^l zoSj;tkd&I9nP;o?e)oPQh0GLNrEpVU1K$GY)Qn7zs-o23D!-8As_bOT6eW8*1)B=1 zirj+S)RIJnirk#MVyg;UC9t_xdBs*BVSOb9u#%E&TP292B76fBob!uP6-@Pvb(0MY z4HeAH^bAc*&CCpS6pRcEjr0wS^bO2(4Na^}&8&>f6`(-LPQj)qCCw_x#SLm#QA(Pv zQbtKhft9{~d3m{Bxv^e;QM$gNrKKgv3?n1mqLeh<;>x^|#0uTKVr7^KE~&-IMVSR9 znfZANAQKal@=Hr>m6V{yR^%4Ijm;~D22*l=u6{*gfxe-h0mw@bzvdSB`r=obn+sQ6 zToROua4%Mai$f|4QuTvUi}Op1l2e0H)09DODzI`cN=+=uFAB-e&w+(vKt_H^esM;A zfr4|enSzFIVsd64NI(;;!`Ii!Gq1QLF)umQ)5TV)BDX*-xZPrk(>$o&6x?nx!>L!_PQgYW6tzfE4HF7-aRV{IsS=bz?GzA6KQ%AK zR;ftI-fsQHkAVyfY>u8Tjv*Dd-rPB#FA~ad;G?U@hL02rFQwO`TMhP^1{|%XFhOwf;DTy0R|>!1p^8w4j!;}N+<>Q~l?BBkBpFZ*C=JNa-JCl#^ zdG6-!UM(+g{r&bt|L;pq*px+WbWw`5Hr~CXuvq1e%+9SDJ$HWD-jUrcZf99xu;u8? zGcOk|EGREoQ=?~D`poCW%a@$<_wL{OezyF%yB)i4%?)E|`}E&_bA{*W`6gY9A`Bkw z+fo1GNavO%T+D_h#>c05uh=nnhrMJ~b#-#eJyA)PwxdBdK_-{BW%0;Z1jw0}{Q2?c zz>&b$FE|z#PTO4g*lnU?!U{g0uNf}s25qW0x7XKO?YLvUcyY4ti#1o%lt2GG_qNu( zuak$#IXH^zY2~(zBcVY%Wjzu**&YY2%&?n&<@Eeq?%3F^A7aYCzbSfof&Km0+UiF& zy8?oX%c7_6_E@{Fa-w;(fp+AM60gLUZ{FxQ8-Kr{yMC{j-+Wu?v^t}|+e)~9KmPac z!?Ru6ct1Y;Wszrnk;&Oxf8wW82Tas z-jVFJ2RL|i&);(YK5N)?EVjujbGkA^@N3?SnKNZ0+qN70 zuaVMk5$Wv;Y≪z~Ql!50~%6Z|S|aujif()R9PFJKU9K@ba(0ayEA7wPD)%;mG`Qu2%i&t-Sw)gHZna`ipbYMf1I z&3#(-ZSs{gZ4MsoDDCjr=FE5A+n2xh_iO8ma^LCOpFUq)f~DGx0mZKF7vHDtuv8@tuZlA z=+f?d@jZw4&%1iK-cZB6zi#`BA6x8g<4X%~Y~pN8ocD$C*eQR3qZ54eH`@CfPt8f) ztjNOVy~Jy(eB7C7`y6$_!DsyP*Qu;pt|~JFCuIXa$L!P3Ysyb&owq5v&kg3!p2hc7 zUO(o!*wvG+j9(X*!uOYE20e>CXZ_(Q7tNe(y9fmKDKd;$^<2@EW3)78&qol`;+07!bdd;kCd