Skip to content

Commit 8af5bb1

Browse files
author
epriestley
committed
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
1 parent dbedb01 commit 8af5bb1

File tree

10 files changed

+242
-1
lines changed

10 files changed

+242
-1
lines changed
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CREATE TABLE phabricator_file.file_transformedfile (
2+
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
3+
originalPHID varchar(64) BINARY NOT NULL,
4+
transform varchar(255) BINARY NOT NULL,
5+
unique key (originalPHID, transform),
6+
transformedPHID varchar(64) BINARY NOT NULL,
7+
key (transformedPHID),
8+
dateCreated INT UNSIGNED NOT NULL,
9+
dateModified INT UNSIGNED NOT NULL
10+
);

src/__phutil_library_map__.php

+4
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@
321321
'PhabricatorFileProxyController' => 'applications/files/controller/proxy',
322322
'PhabricatorFileProxyImage' => 'applications/files/storage/proxyimage',
323323
'PhabricatorFileStorageBlob' => 'applications/files/storage/storageblob',
324+
'PhabricatorFileTransformController' => 'applications/files/controller/transform',
324325
'PhabricatorFileURI' => 'applications/files/uri',
325326
'PhabricatorFileUploadController' => 'applications/files/controller/upload',
326327
'PhabricatorFileViewController' => 'applications/files/controller/view',
@@ -465,6 +466,7 @@
465466
'PhabricatorTimelineEvent' => 'infrastructure/daemon/timeline/storage/event',
466467
'PhabricatorTimelineEventData' => 'infrastructure/daemon/timeline/storage/eventdata',
467468
'PhabricatorTimelineIterator' => 'infrastructure/daemon/timeline/cursor/iterator',
469+
'PhabricatorTransformedFile' => 'applications/files/storage/transformed',
468470
'PhabricatorTypeaheadCommonDatasourceController' => 'applications/typeahead/controller/common',
469471
'PhabricatorTypeaheadDatasourceController' => 'applications/typeahead/controller/base',
470472
'PhabricatorUIExample' => 'applications/uiexample/examples/base',
@@ -760,6 +762,7 @@
760762
'PhabricatorFileProxyController' => 'PhabricatorFileController',
761763
'PhabricatorFileProxyImage' => 'PhabricatorFileDAO',
762764
'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO',
765+
'PhabricatorFileTransformController' => 'PhabricatorFileController',
763766
'PhabricatorFileUploadController' => 'PhabricatorFileController',
764767
'PhabricatorFileViewController' => 'PhabricatorFileController',
765768
'PhabricatorGoodForNothingWorker' => 'PhabricatorWorker',
@@ -883,6 +886,7 @@
883886
'PhabricatorTimelineDAO' => 'PhabricatorLiskDAO',
884887
'PhabricatorTimelineEvent' => 'PhabricatorTimelineDAO',
885888
'PhabricatorTimelineEventData' => 'PhabricatorTimelineDAO',
889+
'PhabricatorTransformedFile' => 'PhabricatorFileDAO',
886890
'PhabricatorTypeaheadCommonDatasourceController' => 'PhabricatorTypeaheadDatasourceController',
887891
'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController',
888892
'PhabricatorUIExampleController' => 'PhabricatorController',

src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php

+2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ public function getURIMap() {
6262
'delete/(?P<id>\d+)/$' => 'PhabricatorFileMacroDeleteController',
6363
),
6464
'proxy/$' => 'PhabricatorFileProxyController',
65+
'xform/(?P<transform>[^/]+)/(?P<phid>[^/]+)/'
66+
=> 'PhabricatorFileTransformController',
6567
),
6668
'/phid/' => array(
6769
'$' => 'PhabricatorPHIDLookupController',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?php
2+
3+
/*
4+
* Copyright 2011 Facebook, Inc.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
class PhabricatorFileTransformController extends PhabricatorFileController {
20+
21+
private $transform;
22+
private $phid;
23+
24+
public function willProcessRequest(array $data) {
25+
$this->transform = $data['transform'];
26+
$this->phid = $data['phid'];
27+
}
28+
29+
public function processRequest() {
30+
31+
$xform = id(new PhabricatorTransformedFile())
32+
->loadOneWhere(
33+
'originalPHID = %s AND transform = %s',
34+
$this->phid,
35+
$this->transform);
36+
37+
if ($xform) {
38+
return $this->buildTransformedFileResponse($xform);
39+
}
40+
41+
$file = id(new PhabricatorFile())->loadOneWhere('phid = %s', $this->phid);
42+
if (!$file) {
43+
return new Aphront404Response();
44+
}
45+
46+
if (!$file->isViewableInBrowser()) {
47+
return new Aphront400Response();
48+
}
49+
50+
if (!$file->isTransformableImage()) {
51+
return new Aphront400Response();
52+
}
53+
54+
switch ($this->transform) {
55+
case 'thumb-160x120':
56+
$xformed_file = $this->executeThumbTransform($file, 160, 120);
57+
break;
58+
case 'thumb-60x45':
59+
$xformed_file = $this->executeThumbTransform($file, 60, 45);
60+
break;
61+
case 'profile-50x50':
62+
$xformed_file = $this->executeProfile50x50Transform($file);
63+
break;
64+
default:
65+
return new Aphront400Response();
66+
}
67+
68+
if (!$xformed_file) {
69+
return new Aphront400Response();
70+
}
71+
72+
$xform = new PhabricatorTransformedFile();
73+
$xform->setOriginalPHID($this->phid);
74+
$xform->setTransform($this->transform);
75+
$xform->setTransformedPHID($xformed_file->getPHID());
76+
$xform->save();
77+
78+
return $this->buildTransformedFileResponse($xform);
79+
}
80+
81+
private function buildTransformedFileResponse(
82+
PhabricatorTransformedFile $xform) {
83+
84+
// TODO: We could just delegate to the file view controller instead,
85+
// which would save the client a roundtrip, but is slightly more complex.
86+
return id(new AphrontRedirectResponse())->setURI(
87+
PhabricatorFileURI::getViewURIForPHID($xform->getTransformedPHID()));
88+
}
89+
90+
private function executeProfile50x50Transform(PhabricatorFile $file) {
91+
$data = $file->loadFileData();
92+
$jpeg = $this->crudelyScaleTo($data, 50, 50);
93+
94+
return PhabricatorFile::newFromFileData($jpeg, array(
95+
'name' => 'profile-'.$file->getName(),
96+
));
97+
}
98+
99+
private function executeThumbTransform(PhabricatorFile $file, $x, $y) {
100+
$data = $file->loadFileData();
101+
$jpeg = $this->crudelyScaleTo($data, $x, $y);
102+
return PhabricatorFile::newFromFileData($jpeg, array(
103+
'name' => 'thumb-'.$file->getName(),
104+
));
105+
}
106+
107+
/**
108+
* Very crudely scale an image up or down to an exact size.
109+
*/
110+
private function crudelyScaleTo($data, $dx, $dy) {
111+
$src = imagecreatefromstring($data);
112+
$x = imagesx($src);
113+
$y = imagesy($src);
114+
115+
$scale = min($x / $dx, $y / $dy);
116+
$dst = imagecreatetruecolor($dx, $dy);
117+
118+
imagecopyresampled(
119+
$dst,
120+
$src,
121+
0, 0,
122+
0, 0,
123+
$dx, $dy,
124+
$scale * $dx, $scale * $dy);
125+
126+
ob_start();
127+
imagejpeg($dst);
128+
return ob_get_clean();
129+
}
130+
131+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
/**
3+
* This file is automatically generated. Lint this module to rebuild it.
4+
* @generated
5+
*/
6+
7+
8+
9+
phutil_require_module('phabricator', 'aphront/response/400');
10+
phutil_require_module('phabricator', 'aphront/response/404');
11+
phutil_require_module('phabricator', 'aphront/response/redirect');
12+
phutil_require_module('phabricator', 'applications/files/controller/base');
13+
phutil_require_module('phabricator', 'applications/files/storage/file');
14+
phutil_require_module('phabricator', 'applications/files/storage/transformed');
15+
phutil_require_module('phabricator', 'applications/files/uri');
16+
17+
phutil_require_module('phutil', 'utils');
18+
19+
20+
phutil_require_source('PhabricatorFileTransformController.php');

src/applications/files/controller/view/PhabricatorFileViewController.php

+30-1
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,37 @@ public function processRequest() {
129129
$panel->appendChild($form);
130130
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
131131

132+
133+
$transformations = id(new PhabricatorTransformedFile())->loadAllWhere(
134+
'originalPHID = %s',
135+
$file->getPHID());
136+
$rows = array();
137+
foreach ($transformations as $transformed) {
138+
$phid = $transformed->getTransformedPHID();
139+
$rows[] = array(
140+
phutil_escape_html($transformed->getTransform()),
141+
phutil_render_tag(
142+
'a',
143+
array(
144+
'href' => PhabricatorFileURI::getViewURIForPHID($phid),
145+
),
146+
$phid));
147+
}
148+
149+
$table = new AphrontTableView($rows);
150+
$table->setHeaders(
151+
array(
152+
'Transform',
153+
'File',
154+
));
155+
156+
$xform_panel = new AphrontPanelView();
157+
$xform_panel->appendChild($table);
158+
$xform_panel->setWidth(AphrontPanelView::WIDTH_FORM);
159+
$xform_panel->setHeader('Transformations');
160+
132161
return $this->buildStandardPageResponse(
133-
array($panel),
162+
array($panel, $xform_panel),
134163
array(
135164
'title' => 'File Info - '.$file->getName(),
136165
));

src/applications/files/controller/view/__init__.php

+4
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@
1111
phutil_require_module('phabricator', 'aphront/response/file');
1212
phutil_require_module('phabricator', 'applications/files/controller/base');
1313
phutil_require_module('phabricator', 'applications/files/storage/file');
14+
phutil_require_module('phabricator', 'applications/files/storage/transformed');
15+
phutil_require_module('phabricator', 'applications/files/uri');
16+
phutil_require_module('phabricator', 'view/control/table');
1417
phutil_require_module('phabricator', 'view/form/base');
1518
phutil_require_module('phabricator', 'view/form/control/static');
1619
phutil_require_module('phabricator', 'view/form/control/submit');
1720
phutil_require_module('phabricator', 'view/layout/panel');
1821

22+
phutil_require_module('phutil', 'markup');
1923
phutil_require_module('phutil', 'utils');
2024

2125

src/applications/files/storage/file/PhabricatorFile.php

+4
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,10 @@ public function isViewableInBrowser() {
206206
return ($this->getViewableMimeType() !== null);
207207
}
208208

209+
public function isTransformableImage() {
210+
return preg_match('@^image/(gif|png|jpe?g)@', $this->getViewableMimeType());
211+
}
212+
209213
public function getViewableMimeType() {
210214
$mime_map = PhabricatorEnv::getEnvConfig('files.viewable-mime-types');
211215

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/*
4+
* Copyright 2011 Facebook, Inc.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
class PhabricatorTransformedFile extends PhabricatorFileDAO {
20+
21+
protected $originalPHID;
22+
protected $transform;
23+
protected $transformedPHID;
24+
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
/**
3+
* This file is automatically generated. Lint this module to rebuild it.
4+
* @generated
5+
*/
6+
7+
8+
9+
phutil_require_module('phabricator', 'applications/files/storage/base');
10+
11+
12+
phutil_require_source('PhabricatorTransformedFile.php');

0 commit comments

Comments
 (0)