mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-29 00:40:57 +01:00
Basic image thumbnailing
Summary: This is still very rough but provides basic support for generating image thumbnails. I need to separate stuff out a bit but I'm going to integrate into Maniphest before I hit the profile stuff so this seems like a reasonable starting point. Test Plan: Generated some image thumbnails in various sizes. Reviewed By: aran Reviewers: jungejason, tuomaspelkonen, aran CC: aran Differential Revision: 333
This commit is contained in:
parent
dbedb012eb
commit
8af5bb117d
10 changed files with 242 additions and 1 deletions
10
resources/sql/patches/040.transform.sql
Normal file
10
resources/sql/patches/040.transform.sql
Normal file
|
@ -0,0 +1,10 @@
|
|||
CREATE TABLE phabricator_file.file_transformedfile (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
originalPHID varchar(64) BINARY NOT NULL,
|
||||
transform varchar(255) BINARY NOT NULL,
|
||||
unique key (originalPHID, transform),
|
||||
transformedPHID varchar(64) BINARY NOT NULL,
|
||||
key (transformedPHID),
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL
|
||||
);
|
|
@ -321,6 +321,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFileProxyController' => 'applications/files/controller/proxy',
|
||||
'PhabricatorFileProxyImage' => 'applications/files/storage/proxyimage',
|
||||
'PhabricatorFileStorageBlob' => 'applications/files/storage/storageblob',
|
||||
'PhabricatorFileTransformController' => 'applications/files/controller/transform',
|
||||
'PhabricatorFileURI' => 'applications/files/uri',
|
||||
'PhabricatorFileUploadController' => 'applications/files/controller/upload',
|
||||
'PhabricatorFileViewController' => 'applications/files/controller/view',
|
||||
|
@ -465,6 +466,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorTimelineEvent' => 'infrastructure/daemon/timeline/storage/event',
|
||||
'PhabricatorTimelineEventData' => 'infrastructure/daemon/timeline/storage/eventdata',
|
||||
'PhabricatorTimelineIterator' => 'infrastructure/daemon/timeline/cursor/iterator',
|
||||
'PhabricatorTransformedFile' => 'applications/files/storage/transformed',
|
||||
'PhabricatorTypeaheadCommonDatasourceController' => 'applications/typeahead/controller/common',
|
||||
'PhabricatorTypeaheadDatasourceController' => 'applications/typeahead/controller/base',
|
||||
'PhabricatorUIExample' => 'applications/uiexample/examples/base',
|
||||
|
@ -760,6 +762,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFileProxyController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileProxyImage' => 'PhabricatorFileDAO',
|
||||
'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO',
|
||||
'PhabricatorFileTransformController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileUploadController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileViewController' => 'PhabricatorFileController',
|
||||
'PhabricatorGoodForNothingWorker' => 'PhabricatorWorker',
|
||||
|
@ -883,6 +886,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorTimelineDAO' => 'PhabricatorLiskDAO',
|
||||
'PhabricatorTimelineEvent' => 'PhabricatorTimelineDAO',
|
||||
'PhabricatorTimelineEventData' => 'PhabricatorTimelineDAO',
|
||||
'PhabricatorTransformedFile' => 'PhabricatorFileDAO',
|
||||
'PhabricatorTypeaheadCommonDatasourceController' => 'PhabricatorTypeaheadDatasourceController',
|
||||
'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController',
|
||||
'PhabricatorUIExampleController' => 'PhabricatorController',
|
||||
|
|
|
@ -62,6 +62,8 @@ class AphrontDefaultApplicationConfiguration
|
|||
'delete/(?P<id>\d+)/$' => 'PhabricatorFileMacroDeleteController',
|
||||
),
|
||||
'proxy/$' => 'PhabricatorFileProxyController',
|
||||
'xform/(?P<transform>[^/]+)/(?P<phid>[^/]+)/'
|
||||
=> 'PhabricatorFileTransformController',
|
||||
),
|
||||
'/phid/' => array(
|
||||
'$' => 'PhabricatorPHIDLookupController',
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
class PhabricatorFileTransformController extends PhabricatorFileController {
|
||||
|
||||
private $transform;
|
||||
private $phid;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->transform = $data['transform'];
|
||||
$this->phid = $data['phid'];
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
|
||||
$xform = id(new PhabricatorTransformedFile())
|
||||
->loadOneWhere(
|
||||
'originalPHID = %s AND transform = %s',
|
||||
$this->phid,
|
||||
$this->transform);
|
||||
|
||||
if ($xform) {
|
||||
return $this->buildTransformedFileResponse($xform);
|
||||
}
|
||||
|
||||
$file = id(new PhabricatorFile())->loadOneWhere('phid = %s', $this->phid);
|
||||
if (!$file) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
if (!$file->isViewableInBrowser()) {
|
||||
return new Aphront400Response();
|
||||
}
|
||||
|
||||
if (!$file->isTransformableImage()) {
|
||||
return new Aphront400Response();
|
||||
}
|
||||
|
||||
switch ($this->transform) {
|
||||
case 'thumb-160x120':
|
||||
$xformed_file = $this->executeThumbTransform($file, 160, 120);
|
||||
break;
|
||||
case 'thumb-60x45':
|
||||
$xformed_file = $this->executeThumbTransform($file, 60, 45);
|
||||
break;
|
||||
case 'profile-50x50':
|
||||
$xformed_file = $this->executeProfile50x50Transform($file);
|
||||
break;
|
||||
default:
|
||||
return new Aphront400Response();
|
||||
}
|
||||
|
||||
if (!$xformed_file) {
|
||||
return new Aphront400Response();
|
||||
}
|
||||
|
||||
$xform = new PhabricatorTransformedFile();
|
||||
$xform->setOriginalPHID($this->phid);
|
||||
$xform->setTransform($this->transform);
|
||||
$xform->setTransformedPHID($xformed_file->getPHID());
|
||||
$xform->save();
|
||||
|
||||
return $this->buildTransformedFileResponse($xform);
|
||||
}
|
||||
|
||||
private function buildTransformedFileResponse(
|
||||
PhabricatorTransformedFile $xform) {
|
||||
|
||||
// TODO: We could just delegate to the file view controller instead,
|
||||
// which would save the client a roundtrip, but is slightly more complex.
|
||||
return id(new AphrontRedirectResponse())->setURI(
|
||||
PhabricatorFileURI::getViewURIForPHID($xform->getTransformedPHID()));
|
||||
}
|
||||
|
||||
private function executeProfile50x50Transform(PhabricatorFile $file) {
|
||||
$data = $file->loadFileData();
|
||||
$jpeg = $this->crudelyScaleTo($data, 50, 50);
|
||||
|
||||
return PhabricatorFile::newFromFileData($jpeg, array(
|
||||
'name' => 'profile-'.$file->getName(),
|
||||
));
|
||||
}
|
||||
|
||||
private function executeThumbTransform(PhabricatorFile $file, $x, $y) {
|
||||
$data = $file->loadFileData();
|
||||
$jpeg = $this->crudelyScaleTo($data, $x, $y);
|
||||
return PhabricatorFile::newFromFileData($jpeg, array(
|
||||
'name' => 'thumb-'.$file->getName(),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Very crudely scale an image up or down to an exact size.
|
||||
*/
|
||||
private function crudelyScaleTo($data, $dx, $dy) {
|
||||
$src = imagecreatefromstring($data);
|
||||
$x = imagesx($src);
|
||||
$y = imagesy($src);
|
||||
|
||||
$scale = min($x / $dx, $y / $dy);
|
||||
$dst = imagecreatetruecolor($dx, $dy);
|
||||
|
||||
imagecopyresampled(
|
||||
$dst,
|
||||
$src,
|
||||
0, 0,
|
||||
0, 0,
|
||||
$dx, $dy,
|
||||
$scale * $dx, $scale * $dy);
|
||||
|
||||
ob_start();
|
||||
imagejpeg($dst);
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
}
|
20
src/applications/files/controller/transform/__init__.php
Normal file
20
src/applications/files/controller/transform/__init__.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'aphront/response/400');
|
||||
phutil_require_module('phabricator', 'aphront/response/404');
|
||||
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||
phutil_require_module('phabricator', 'applications/files/controller/base');
|
||||
phutil_require_module('phabricator', 'applications/files/storage/file');
|
||||
phutil_require_module('phabricator', 'applications/files/storage/transformed');
|
||||
phutil_require_module('phabricator', 'applications/files/uri');
|
||||
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorFileTransformController.php');
|
|
@ -129,8 +129,37 @@ class PhabricatorFileViewController extends PhabricatorFileController {
|
|||
$panel->appendChild($form);
|
||||
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
|
||||
|
||||
|
||||
$transformations = id(new PhabricatorTransformedFile())->loadAllWhere(
|
||||
'originalPHID = %s',
|
||||
$file->getPHID());
|
||||
$rows = array();
|
||||
foreach ($transformations as $transformed) {
|
||||
$phid = $transformed->getTransformedPHID();
|
||||
$rows[] = array(
|
||||
phutil_escape_html($transformed->getTransform()),
|
||||
phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => PhabricatorFileURI::getViewURIForPHID($phid),
|
||||
),
|
||||
$phid));
|
||||
}
|
||||
|
||||
$table = new AphrontTableView($rows);
|
||||
$table->setHeaders(
|
||||
array(
|
||||
'Transform',
|
||||
'File',
|
||||
));
|
||||
|
||||
$xform_panel = new AphrontPanelView();
|
||||
$xform_panel->appendChild($table);
|
||||
$xform_panel->setWidth(AphrontPanelView::WIDTH_FORM);
|
||||
$xform_panel->setHeader('Transformations');
|
||||
|
||||
return $this->buildStandardPageResponse(
|
||||
array($panel),
|
||||
array($panel, $xform_panel),
|
||||
array(
|
||||
'title' => 'File Info - '.$file->getName(),
|
||||
));
|
||||
|
|
|
@ -11,11 +11,15 @@ phutil_require_module('phabricator', 'aphront/response/404');
|
|||
phutil_require_module('phabricator', 'aphront/response/file');
|
||||
phutil_require_module('phabricator', 'applications/files/controller/base');
|
||||
phutil_require_module('phabricator', 'applications/files/storage/file');
|
||||
phutil_require_module('phabricator', 'applications/files/storage/transformed');
|
||||
phutil_require_module('phabricator', 'applications/files/uri');
|
||||
phutil_require_module('phabricator', 'view/control/table');
|
||||
phutil_require_module('phabricator', 'view/form/base');
|
||||
phutil_require_module('phabricator', 'view/form/control/static');
|
||||
phutil_require_module('phabricator', 'view/form/control/submit');
|
||||
phutil_require_module('phabricator', 'view/layout/panel');
|
||||
|
||||
phutil_require_module('phutil', 'markup');
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
|
|
|
@ -206,6 +206,10 @@ class PhabricatorFile extends PhabricatorFileDAO {
|
|||
return ($this->getViewableMimeType() !== null);
|
||||
}
|
||||
|
||||
public function isTransformableImage() {
|
||||
return preg_match('@^image/(gif|png|jpe?g)@', $this->getViewableMimeType());
|
||||
}
|
||||
|
||||
public function getViewableMimeType() {
|
||||
$mime_map = PhabricatorEnv::getEnvConfig('files.viewable-mime-types');
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
class PhabricatorTransformedFile extends PhabricatorFileDAO {
|
||||
|
||||
protected $originalPHID;
|
||||
protected $transform;
|
||||
protected $transformedPHID;
|
||||
|
||||
}
|
12
src/applications/files/storage/transformed/__init__.php
Normal file
12
src/applications/files/storage/transformed/__init__.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'applications/files/storage/base');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorTransformedFile.php');
|
Loading…
Reference in a new issue