1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-02-18 09:48:39 +01:00

Convert "profile" image transforms to the new pathway

Summary:
Ref T7707. This ends up being sort of complicated: to support 100x100 images in T4406, we need to scale small images //up// so they look OK when we scale them back down with `background-size` in CSS.

The rest of it is mostly straightforward.

Test Plan:
  - Did an OAuth handshake and saw a scaled-up, scaled-down profile picture that looked correct.
  - Used Pholio, edited pholio, embedded pholio.
  - Uploaded a bunch of small/weird/big images and regenerated all their transforms.
  - Uploaded some text files into Pholio.
  - Grepped for removed methods, etc.

Reviewers: chad, btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T7707

Differential Revision: https://secure.phabricator.com/D12818
This commit is contained in:
epriestley 2015-05-12 15:50:46 -07:00
parent 75f6211233
commit 7e365eb8ae
16 changed files with 115 additions and 141 deletions

View file

@ -89,13 +89,17 @@ final class PhabricatorAuthAccountView extends AphrontView {
$account_uri); $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( return phutil_tag(
'div', 'div',
array( array(
'class' => 'auth-account-view', 'class' => 'auth-account-view',
'style' => 'background-image: url('.$image_uri.')', 'style' => 'background-image: url('.$image_uri.');',
), ),
$content); $content);
} }

View file

@ -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( public function executeProfileTransform(
PhabricatorFile $file, PhabricatorFile $file,
$x, $x,
@ -123,21 +108,6 @@ final class PhabricatorImageTransformer {
return self::saveImageDataInAnyFormat($dst, $file->getMimeType()); 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) { private function getBlankDestinationFile($dx, $dy) {
$dst = imagecreatetruecolor($dx, $dy); $dst = imagecreatetruecolor($dx, $dy);
imagesavealpha($dst, true); imagesavealpha($dst, true);

View file

@ -44,50 +44,33 @@ final class PhabricatorFileTransformController
} }
} }
$type = $file->getMimeType(); $xforms = PhabricatorFileTransform::getAllTransforms();
if (!isset($xforms[$transform])) {
if (!$file->isViewableInBrowser() || !$file->isTransformableImage()) { return new Aphront404Response();
return $this->buildDefaultTransformation($file, $transform);
} }
$xform = $xforms[$transform];
// We're essentially just building a cache here and don't need CSRF // We're essentially just building a cache here and don't need CSRF
// protection. // protection.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$xformed_file = null; $xformed_file = null;
if ($xform->canApplyTransform($file)) {
$xforms = PhabricatorFileTransform::getAllTransforms(); try {
if (isset($xforms[$transform])) { $xformed_file = $xforms[$transform]->applyTransform($file);
$xform = $xforms[$transform]; } catch (Exception $ex) {
if ($xform->canApplyTransform($file)) { // In normal transform mode, we ignore failures and generate a
try { // default transform below. If we're explicitly regenerating the
$xformed_file = $xforms[$transform]->applyTransform($file); // thumbnail, rethrow the exception.
} catch (Exception $ex) { if ($is_regenerate) {
// In normal transform mode, we ignore failures and generate a throw $ex;
// 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) { if (!$xformed_file) {
switch ($transform) { $xformed_file = $xform->getDefaultTransform($file);
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();
}
} }
if (!$xformed_file) { if (!$xformed_file) {
@ -103,40 +86,6 @@ final class PhabricatorFileTransformController
return $this->buildTransformedFileResponse($xform); 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( private function buildTransformedFileResponse(
PhabricatorTransformedFile $xform) { PhabricatorTransformedFile $xform) {
@ -154,11 +103,6 @@ final class PhabricatorFileTransformController
return $file->getRedirectResponse(); return $file->getRedirectResponse();
} }
private function executeThumbTransform(PhabricatorFile $file, $x, $y) {
$xformer = new PhabricatorImageTransformer();
return $xformer->executeThumbTransform($file, $x, $y);
}
private function destroyTransform(PhabricatorTransformedFile $xform) { private function destroyTransform(PhabricatorTransformedFile $xform) {
$file = id(new PhabricatorFileQuery()) $file = id(new PhabricatorFileQuery())
->setViewer(PhabricatorUser::getOmnipotentUser()) ->setViewer(PhabricatorUser::getOmnipotentUser())

View file

@ -784,14 +784,6 @@ final class PhabricatorFile extends PhabricatorFileDAO
return PhabricatorEnv::getCDNURI($path); return PhabricatorEnv::getCDNURI($path);
} }
public function getProfileThumbURI() {
return $this->getTransformedURI('thumb-profile');
}
public function getThumb280x210URI() {
return $this->getTransformedURI('thumb-280x210');
}
public function isViewableInBrowser() { public function isViewableInBrowser() {
return ($this->getViewableMimeType() !== null); return ($this->getViewableMimeType() !== null);
} }

View file

@ -42,13 +42,20 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform {
$dst_w, $dst_h, $dst_w, $dst_h,
$src_x, $src_y, $src_x, $src_y,
$src_w, $src_h, $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_x = ($dst_w - $cpy_w) / 2;
$off_y = ($dst_h - $cpy_h) / 2; $off_y = ($dst_h - $cpy_h) / 2;
@ -58,11 +65,18 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform {
$argv[] = '-shave'; $argv[] = '-shave';
$argv[] = $src_x.'x'.$src_y; $argv[] = $src_x.'x'.$src_y;
$argv[] = '-resize'; $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[] = '-bordercolor';
$argv[] = 'rgba(255, 255, 255, 0)'; $argv[] = 'rgba(255, 255, 255, 0)';
$argv[] = '-border'; $argv[] = '-border';
$argv[] = $off_x.'x'.$off_y; $argv[] = $off_x.'x'.$off_y;
return $this->applyImagemagick($argv); return $this->applyImagemagick($argv);
} }
@ -117,7 +131,13 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform {
* @param string Raw file data. * @param string Raw file data.
*/ */
protected function newFileFromData($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( return PhabricatorFile::newFromFileData(
$data, $data,

View file

@ -12,6 +12,7 @@ final class PhabricatorFileThumbnailTransform
private $key; private $key;
private $dstX; private $dstX;
private $dstY; private $dstY;
private $scaleUp;
public function setName($name) { public function setName($name) {
$this->name = $name; $this->name = $name;
@ -29,6 +30,11 @@ final class PhabricatorFileThumbnailTransform
return $this; return $this;
} }
public function setScaleUp($scale) {
$this->scaleUp = $scale;
return $this;
}
public function getTransformName() { public function getTransformName() {
return $this->name; return $this->name;
} }
@ -42,7 +48,8 @@ final class PhabricatorFileThumbnailTransform
id(new PhabricatorFileThumbnailTransform()) id(new PhabricatorFileThumbnailTransform())
->setName(pht("Profile (100px \xC3\x97 100px)")) ->setName(pht("Profile (100px \xC3\x97 100px)"))
->setKey(self::TRANSFORM_PROFILE) ->setKey(self::TRANSFORM_PROFILE)
->setDimensions(100, 100), ->setDimensions(100, 100)
->setScaleUp(true),
id(new PhabricatorFileThumbnailTransform()) id(new PhabricatorFileThumbnailTransform())
->setName(pht("Pinboard (280px \xC3\x97 210px)")) ->setName(pht("Pinboard (280px \xC3\x97 210px)"))
->setKey(self::TRANSFORM_PINBOARD) ->setKey(self::TRANSFORM_PINBOARD)
@ -86,7 +93,8 @@ final class PhabricatorFileThumbnailTransform
$copy_x, $copy_x,
$copy_y, $copy_y,
$use_x, $use_x,
$use_y); $use_y,
$this->scaleUp);
} }
@ -144,22 +152,35 @@ final class PhabricatorFileThumbnailTransform
$copy_x = $src_x; $copy_x = $src_x;
$copy_y = $src_y; $copy_y = $src_y;
} else { } else {
$scale_up = $this->scaleUp;
// Otherwise, both dimensions are fixed. Figure out how much we'd have to // 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 // scale the image down along each dimension to get the entire thing to
// fit. // fit.
$scale_x = min(($dst_x / $src_x), 1); $scale_x = ($dst_x / $src_x);
$scale_y = min(($dst_y / $src_y), 1); $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) { if ($scale_x > $scale_y) {
// This image is relatively tall and narrow. We're going to crop off the // This image is relatively tall and narrow. We're going to crop off the
// top and bottom. // top and bottom.
$copy_x = $src_x; $scale = $scale_x;
$copy_y = min($src_y, $dst_y / $scale_x);
} else { } else {
// This image is relatively short and wide. We're going to crop off the // This image is relatively short and wide. We're going to crop off the
// left and right. // left and right.
$copy_x = min($src_x, $dst_x / $scale_y); $scale = $scale_y;
$copy_y = $src_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 // In this mode, we always use the entire destination image. We may

View file

@ -179,14 +179,18 @@ final class PhabricatorMacroSearchEngine
assert_instances_of($macros, 'PhabricatorFileImageMacro'); assert_instances_of($macros, 'PhabricatorFileImageMacro');
$viewer = $this->requireViewer(); $viewer = $this->requireViewer();
$xform = PhabricatorFileTransform::getTransformByKey(
PhabricatorFileThumbnailTransform::TRANSFORM_PINBOARD);
$pinboard = new PHUIPinboardView(); $pinboard = new PHUIPinboardView();
foreach ($macros as $macro) { foreach ($macros as $macro) {
$file = $macro->getFile(); $file = $macro->getFile();
$item = new PHUIPinboardItemView(); $item = new PHUIPinboardItemView();
if ($file) { if ($file) {
$item->setImageURI($file->getThumb280x210URI()); $item->setImageURI($file->getURIForTransform($xform));
$item->setImageSize(280, 210); list($x, $y) = $xform->getTransformedDimensions($file);
$item->setImageSize($x, $y);
} }
if ($macro->getDateCreated()) { if ($macro->getDateCreated()) {

View file

@ -141,15 +141,22 @@ final class PholioMockSearchEngine extends PhabricatorApplicationSearchEngine {
$viewer = $this->requireViewer(); $viewer = $this->requireViewer();
$xform = PhabricatorFileTransform::getTransformByKey(
PhabricatorFileThumbnailTransform::TRANSFORM_PINBOARD);
$board = new PHUIPinboardView(); $board = new PHUIPinboardView();
foreach ($mocks as $mock) { foreach ($mocks as $mock) {
$image = $mock->getCoverFile();
$image_uri = $image->getURIForTransform($xform);
list($x, $y) = $xform->getTransformedDimensions($image);
$header = 'M'.$mock->getID().' '.$mock->getName(); $header = 'M'.$mock->getID().' '.$mock->getName();
$item = id(new PHUIPinboardItemView()) $item = id(new PHUIPinboardItemView())
->setHeader($header) ->setHeader($header)
->setURI('/M'.$mock->getID()) ->setURI('/M'.$mock->getID())
->setImageURI($mock->getCoverFile()->getThumb280x210URI()) ->setImageURI($image_uri)
->setImageSize(280, 210) ->setImageSize($x, $y)
->setDisabled($mock->isClosed()) ->setDisabled($mock->isClosed())
->addIconCount('fa-picture-o', count($mock->getImages())) ->addIconCount('fa-picture-o', count($mock->getImages()))
->addIconCount('fa-trophy', $mock->getTokenCount()); ->addIconCount('fa-trophy', $mock->getTokenCount());

View file

@ -28,25 +28,29 @@ final class PholioMockEmbedView extends AphrontView {
$this->mock->getImages(), array_flip($this->images)); $this->mock->getImages(), array_flip($this->images));
} }
$xform = PhabricatorFileTransform::getTransformByKey(
PhabricatorFileThumbnailTransform::TRANSFORM_PINBOARD);
if ($images_to_show) { if ($images_to_show) {
foreach ($images_to_show as $image) { $image = head($images_to_show);
$thumbfile = $image->getFile(); $thumbfile = $image->getFile();
$thumbnail = $thumbfile->getThumb280x210URI();
}
$header = 'M'.$mock->getID().' '.$mock->getName(). $header = 'M'.$mock->getID().' '.$mock->getName().
' (#'.$image->getID().')'; ' (#'.$image->getID().')';
$uri = '/M'.$this->mock->getID().'/'.$image->getID().'/'; $uri = '/M'.$this->mock->getID().'/'.$image->getID().'/';
} else { } else {
$thumbnail = $mock->getCoverFile()->getThumb280x210URI(); $thumbfile = $mock->getCoverFile();
$header = 'M'.$mock->getID().' '.$mock->getName(); $header = 'M'.$mock->getID().' '.$mock->getName();
$uri = '/M'.$this->mock->getID(); $uri = '/M'.$this->mock->getID();
} }
$thumbnail = $thumbfile->getURIForTransform($xform);
list($x, $y) = $xform->getTransformedDimensions($thumbfile);
$item = id(new PHUIPinboardItemView()) $item = id(new PHUIPinboardItemView())
->setHeader($header) ->setHeader($header)
->setURI($uri) ->setURI($uri)
->setImageURI($thumbnail) ->setImageURI($thumbnail)
->setImageSize(280, 210) ->setImageSize($x, $y)
->setDisabled($mock->isClosed()) ->setDisabled($mock->isClosed())
->addIconCount('fa-picture-o', count($mock->getImages())) ->addIconCount('fa-picture-o', count($mock->getImages()))
->addIconCount('fa-trophy', $mock->getTokenCount()); ->addIconCount('fa-trophy', $mock->getTokenCount());

View file

@ -68,8 +68,11 @@ final class PholioMockImagesView extends AphrontView {
// TODO: We could maybe do a better job with tailoring this, which is the // TODO: We could maybe do a better job with tailoring this, which is the
// image shown on the review stage. // image shown on the review stage.
$nonimage_uri = celerity_get_resource_uri( $default_name = 'image-100x100.png';
'rsrc/image/icon/fatcow/thumbnails/default.p100.png'); $builtins = PhabricatorFile::loadBuiltins(
$this->getUser(),
array($default_name));
$default = $builtins[$default_name];
$engine = id(new PhabricatorMarkupEngine()) $engine = id(new PhabricatorMarkupEngine())
->setViewer($this->getUser()); ->setViewer($this->getUser());
@ -97,7 +100,7 @@ final class PholioMockImagesView extends AphrontView {
'fullURI' => $file->getBestURI(), 'fullURI' => $file->getBestURI(),
'stageURI' => ($file->isViewableImage() 'stageURI' => ($file->isViewableImage()
? $file->getBestURI() ? $file->getBestURI()
: $nonimage_uri), : $default->getBestURI()),
'pageURI' => $this->getImagePageURI($image, $mock), 'pageURI' => $this->getImagePageURI($image, $mock),
'downloadURI' => $file->getDownloadURI(), 'downloadURI' => $file->getDownloadURI(),
'historyURI' => $history_uri, 'historyURI' => $history_uri,

View file

@ -38,11 +38,15 @@ final class PholioUploadedImageView extends AphrontView {
->setSigil('image-description') ->setSigil('image-description')
->setLabel(pht('Description')); ->setLabel(pht('Description'));
$xform = PhabricatorFileTransform::getTransformByKey(
PhabricatorFileThumbnailTransform::TRANSFORM_PINBOARD);
$thumbnail_uri = $file->getURIForTransform($xform);
$thumb_frame = phutil_tag( $thumb_frame = phutil_tag(
'div', 'div',
array( array(
'class' => 'pholio-thumb-frame', 'class' => 'pholio-thumb-frame',
'style' => 'background-image: url('.$file->getThumb280x210URI().');', 'style' => 'background-image: url('.$thumbnail_uri.');',
)); ));
$handle = javelin_tag( $handle = javelin_tag(

View file

@ -28,6 +28,7 @@
border: 1px solid {$lightblueborder}; border: 1px solid {$lightblueborder};
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: 4px 4px; background-position: 4px 4px;
background-size: 50px 50px;
padding: 4px 4px 4px 62px; padding: 4px 4px 4px 62px;
min-height: 50px; min-height: 50px;
border-radius: 2px; border-radius: 2px;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB