diff --git a/src/applications/auth/view/PhabricatorAuthAccountView.php b/src/applications/auth/view/PhabricatorAuthAccountView.php --- a/src/applications/auth/view/PhabricatorAuthAccountView.php +++ b/src/applications/auth/view/PhabricatorAuthAccountView.php @@ -89,13 +89,17 @@ $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 --- a/src/applications/files/PhabricatorImageTransformer.php +++ b/src/applications/files/PhabricatorImageTransformer.php @@ -20,21 +20,6 @@ )); } - 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 @@ 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 --- a/src/applications/files/controller/PhabricatorFileTransformController.php +++ b/src/applications/files/controller/PhabricatorFileTransformController.php @@ -44,50 +44,33 @@ } } - $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 @@ 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 @@ 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 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -784,14 +784,6 @@ 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 --- a/src/applications/files/transform/PhabricatorFileImageTransform.php +++ b/src/applications/files/transform/PhabricatorFileImageTransform.php @@ -42,13 +42,20 @@ $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 @@ $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 @@ * @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 --- a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php +++ b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php @@ -12,6 +12,7 @@ private $key; private $dstX; private $dstY; + private $scaleUp; public function setName($name) { $this->name = $name; @@ -29,6 +30,11 @@ return $this; } + public function setScaleUp($scale) { + $this->scaleUp = $scale; + return $this; + } + public function getTransformName() { return $this->name; } @@ -42,7 +48,8 @@ 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 @@ $copy_x, $copy_y, $use_x, - $use_y); + $use_y, + $this->scaleUp); } @@ -144,22 +152,35 @@ $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 --- a/src/applications/macro/query/PhabricatorMacroSearchEngine.php +++ b/src/applications/macro/query/PhabricatorMacroSearchEngine.php @@ -179,14 +179,18 @@ 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 --- a/src/applications/pholio/query/PholioMockSearchEngine.php +++ b/src/applications/pholio/query/PholioMockSearchEngine.php @@ -141,15 +141,22 @@ $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 --- a/src/applications/pholio/view/PholioMockEmbedView.php +++ b/src/applications/pholio/view/PholioMockEmbedView.php @@ -28,25 +28,29 @@ $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 --- a/src/applications/pholio/view/PholioMockImagesView.php +++ b/src/applications/pholio/view/PholioMockImagesView.php @@ -68,8 +68,11 @@ // 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 @@ '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 --- a/src/applications/pholio/view/PholioUploadedImageView.php +++ b/src/applications/pholio/view/PholioUploadedImageView.php @@ -38,11 +38,15 @@ ->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 --- 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@