PreviewTest.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963
  1. <?php
  2. /**
  3. * @author Georg Ehrke <georg@owncloud.com>
  4. * @author Olivier Paroz <owncloud@interfasys.ch>
  5. *
  6. * @copyright Copyright (c) 2015, ownCloud, Inc.
  7. * @license AGPL-3.0
  8. *
  9. * This code is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU Affero General Public License, version 3,
  11. * as published by the Free Software Foundation.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU Affero General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public License, version 3,
  19. * along with this program. If not, see <http://www.gnu.org/licenses/>
  20. *
  21. */
  22. namespace Test;
  23. use OC\Files\FileInfo;
  24. use OC\Files\Filesystem;
  25. use OC\Files\Storage\Temporary;
  26. use OC\Files\View;
  27. use OC\Preview;
  28. use Test\Traits\MountProviderTrait;
  29. use Test\Traits\UserTrait;
  30. /**
  31. * Class PreviewTest
  32. *
  33. * @group DB
  34. *
  35. * @package Test
  36. */
  37. class PreviewTest extends TestCase {
  38. use UserTrait;
  39. use MountProviderTrait;
  40. const TEST_PREVIEW_USER1 = "test-preview-user1";
  41. /** @var View */
  42. private $rootView;
  43. /**
  44. * Note that using 756 with an image with a ratio of 1.6 brings interesting rounding issues
  45. *
  46. * @var int maximum width allowed for a preview
  47. * */
  48. private $configMaxWidth = 756;
  49. /** @var int maximum height allowed for a preview */
  50. private $configMaxHeight = 756;
  51. private $keepAspect;
  52. private $scalingUp;
  53. private $samples = [];
  54. private $sampleFileId;
  55. private $sampleFilename;
  56. private $sampleWidth;
  57. private $sampleHeight;
  58. private $maxScaleFactor;
  59. /** @var int width of the max preview */
  60. private $maxPreviewWidth;
  61. /** @var int height of the max preview */
  62. private $maxPreviewHeight;
  63. /** @var int height of the max preview, which is the same as the one of the original image */
  64. private $maxPreviewRatio;
  65. private $cachedBigger = [];
  66. /**
  67. * Make sure your configuration file doesn't contain any additional providers
  68. */
  69. protected function setUp() {
  70. parent::setUp();
  71. $this->createUser(self::TEST_PREVIEW_USER1, self::TEST_PREVIEW_USER1);
  72. $this->loginAsUser(self::TEST_PREVIEW_USER1);
  73. $storage = new Temporary([]);
  74. Filesystem::mount($storage, [], '/' . self::TEST_PREVIEW_USER1 . '/');
  75. $this->rootView = new View('');
  76. $this->rootView->mkdir('/' . self::TEST_PREVIEW_USER1);
  77. $this->rootView->mkdir('/' . self::TEST_PREVIEW_USER1 . '/files');
  78. // We simulate the max dimension set in the config
  79. \OC::$server->getConfig()
  80. ->setSystemValue('preview_max_x', $this->configMaxWidth);
  81. \OC::$server->getConfig()
  82. ->setSystemValue('preview_max_y', $this->configMaxHeight);
  83. // Used to test upscaling
  84. $this->maxScaleFactor = 2;
  85. \OC::$server->getConfig()
  86. ->setSystemValue('preview_max_scale_factor', $this->maxScaleFactor);
  87. // We need to enable the providers we're going to use in the tests
  88. $providers = [
  89. 'OC\\Preview\\JPEG',
  90. 'OC\\Preview\\PNG',
  91. 'OC\\Preview\\GIF',
  92. 'OC\\Preview\\TXT',
  93. 'OC\\Preview\\Postscript'
  94. ];
  95. \OC::$server->getConfig()
  96. ->setSystemValue('enabledPreviewProviders', $providers);
  97. // Sample is 1680x1050 JPEG
  98. $this->prepareSample('testimage.jpg', 1680, 1050);
  99. // Sample is 2400x1707 EPS
  100. $this->prepareSample('testimage.eps', 2400, 1707);
  101. // Sample is 1200x450 PNG
  102. $this->prepareSample('testimage-wide.png', 1200, 450);
  103. // Sample is 64x64 GIF
  104. $this->prepareSample('testimage.gif', 64, 64);
  105. }
  106. protected function tearDown() {
  107. $this->logout();
  108. parent::tearDown();
  109. }
  110. /**
  111. * Tests if a preview can be deleted
  112. */
  113. public function testIsPreviewDeleted() {
  114. $sampleFile = '/' . self::TEST_PREVIEW_USER1 . '/files/test.txt';
  115. $this->rootView->file_put_contents($sampleFile, 'dummy file data');
  116. $x = 50;
  117. $y = 50;
  118. $preview = new Preview(self::TEST_PREVIEW_USER1, 'files/', 'test.txt', $x, $y);
  119. $preview->getPreview();
  120. $fileInfo = $this->rootView->getFileInfo($sampleFile);
  121. /** @var int $fileId */
  122. $fileId = $fileInfo['fileid'];
  123. $thumbCacheFile = $this->buildCachePath($fileId, $x, $y, true);
  124. $this->assertSame(
  125. true, $this->rootView->file_exists($thumbCacheFile), "$thumbCacheFile \n"
  126. );
  127. $preview->deletePreview();
  128. $this->assertSame(false, $this->rootView->file_exists($thumbCacheFile));
  129. }
  130. /**
  131. * Tests if all previews can be deleted
  132. *
  133. * We test this first to make sure we'll be able to cleanup after each preview generating test
  134. */
  135. public function testAreAllPreviewsDeleted() {
  136. $sampleFile = '/' . self::TEST_PREVIEW_USER1 . '/files/test.txt';
  137. $this->rootView->file_put_contents($sampleFile, 'dummy file data');
  138. $x = 50;
  139. $y = 50;
  140. $preview = new Preview(self::TEST_PREVIEW_USER1, 'files/', 'test.txt', $x, $y);
  141. $preview->getPreview();
  142. $fileInfo = $this->rootView->getFileInfo($sampleFile);
  143. /** @var int $fileId */
  144. $fileId = $fileInfo['fileid'];
  145. $thumbCacheFolder = '/' . self::TEST_PREVIEW_USER1 . '/' . Preview::THUMBNAILS_FOLDER .
  146. '/' . $fileId . '/';
  147. $this->assertSame(true, $this->rootView->is_dir($thumbCacheFolder), "$thumbCacheFolder \n");
  148. $preview->deleteAllPreviews();
  149. $this->assertSame(false, $this->rootView->is_dir($thumbCacheFolder));
  150. }
  151. public function txtBlacklist() {
  152. $txt = 'random text file';
  153. return [
  154. ['txt', $txt, false],
  155. ];
  156. }
  157. /**
  158. * @dataProvider txtBlacklist
  159. *
  160. * @param $extension
  161. * @param $data
  162. * @param $expectedResult
  163. */
  164. public function testIsTransparent($extension, $data, $expectedResult) {
  165. $x = 32;
  166. $y = 32;
  167. $sample = '/' . self::TEST_PREVIEW_USER1 . '/files/test.' . $extension;
  168. $this->rootView->file_put_contents($sample, $data);
  169. $preview = new Preview(
  170. self::TEST_PREVIEW_USER1, 'files/', 'test.' . $extension, $x,
  171. $y
  172. );
  173. $image = $preview->getPreview();
  174. $resource = $image->resource();
  175. //http://stackoverflow.com/questions/5702953/imagecolorat-and-transparency
  176. $colorIndex = imagecolorat($resource, 1, 1);
  177. $colorInfo = imagecolorsforindex($resource, $colorIndex);
  178. $this->assertSame(
  179. $expectedResult,
  180. $colorInfo['alpha'] === 127,
  181. 'Failed asserting that only previews for text files are transparent.'
  182. );
  183. }
  184. /**
  185. * Tests if unsupported previews return an empty object
  186. */
  187. public function testUnsupportedPreviewsReturnEmptyObject() {
  188. $width = 400;
  189. $height = 200;
  190. // Previews for odt files are not enabled
  191. $imgData = file_get_contents(\OC::$SERVERROOT . '/tests/data/testimage.odt');
  192. $imgPath = '/' . self::TEST_PREVIEW_USER1 . '/files/testimage.odt';
  193. $this->rootView->file_put_contents($imgPath, $imgData);
  194. $preview =
  195. new Preview(self::TEST_PREVIEW_USER1, 'files/', 'testimage.odt', $width, $height);
  196. $preview->getPreview();
  197. $image = $preview->getPreview();
  198. $this->assertSame(false, $image->valid());
  199. }
  200. /**
  201. * We generate the data to use as it makes it easier to adjust in case we need to test
  202. * something different
  203. *
  204. * @return array
  205. */
  206. public static function dimensionsDataProvider() {
  207. $data = [];
  208. $samples = [
  209. [200, 800],
  210. [200, 800],
  211. [50, 400],
  212. [4, 60],
  213. ];
  214. $keepAspect = false;
  215. $scalingUp = false;
  216. for ($a = 0; $a < sizeof($samples); $a++) {
  217. for ($b = 0; $b < 2; $b++) {
  218. for ($c = 0; $c < 2; $c++) {
  219. for ($d = 0; $d < 4; $d++) {
  220. $coordinates = [
  221. [
  222. -rand($samples[$a][0], $samples[$a][1]),
  223. -rand($samples[$a][0], $samples[$a][1])
  224. ],
  225. [
  226. rand($samples[$a][0], $samples[$a][1]),
  227. rand($samples[$a][0], $samples[$a][1])
  228. ],
  229. [
  230. -rand($samples[$a][0], $samples[$a][1]),
  231. rand($samples[$a][0], $samples[$a][1])
  232. ],
  233. [
  234. rand($samples[$a][0], $samples[$a][1]),
  235. -rand($samples[$a][0], $samples[$a][1])
  236. ]
  237. ];
  238. $row = [$a];
  239. $row[] = $coordinates[$d][0];
  240. $row[] = $coordinates[$d][1];
  241. $row[] = $keepAspect;
  242. $row[] = $scalingUp;
  243. $data[] = $row;
  244. }
  245. $scalingUp = !$scalingUp;
  246. }
  247. $keepAspect = !$keepAspect;
  248. }
  249. }
  250. return $data;
  251. }
  252. /**
  253. * Tests if a preview of max dimensions gets created
  254. *
  255. * @requires extension imagick
  256. * @dataProvider dimensionsDataProvider
  257. *
  258. * @param int $sampleId
  259. * @param int $widthAdjustment
  260. * @param int $heightAdjustment
  261. * @param bool $keepAspect
  262. * @param bool $scalingUp
  263. */
  264. public function testCreateMaxAndNormalPreviewsAtFirstRequest(
  265. $sampleId, $widthAdjustment, $heightAdjustment, $keepAspect = false, $scalingUp = false
  266. ) {
  267. // Get the right sample for the experiment
  268. $this->getSample($sampleId);
  269. $sampleWidth = $this->sampleWidth;
  270. $sampleHeight = $this->sampleHeight;
  271. $sampleFileId = $this->sampleFileId;
  272. // Adjust the requested size so that we trigger various test cases
  273. $previewWidth = $sampleWidth + $widthAdjustment;
  274. $previewHeight = $sampleHeight + $heightAdjustment;
  275. $this->keepAspect = $keepAspect;
  276. $this->scalingUp = $scalingUp;
  277. // Generates the max preview
  278. $preview = $this->createPreview($previewWidth, $previewHeight);
  279. // There should be no cached thumbnails
  280. $thumbnailFolder = '/' . self::TEST_PREVIEW_USER1 . '/' . Preview::THUMBNAILS_FOLDER .
  281. '/' . $sampleFileId;
  282. $this->assertSame(false, $this->rootView->is_dir($thumbnailFolder));
  283. $image = $preview->getPreview();
  284. $this->assertNotSame(false, $image);
  285. $maxThumbCacheFile = $this->buildCachePath(
  286. $sampleFileId, $this->maxPreviewWidth, $this->maxPreviewHeight, true, '-max'
  287. );
  288. $this->assertSame(
  289. true, $this->rootView->file_exists($maxThumbCacheFile), "$maxThumbCacheFile \n"
  290. );
  291. // We check the dimensions of the file we've just stored
  292. $maxPreview = imagecreatefromstring($this->rootView->file_get_contents($maxThumbCacheFile));
  293. $this->assertEquals($this->maxPreviewWidth, imagesx($maxPreview));
  294. $this->assertEquals($this->maxPreviewHeight, imagesy($maxPreview));
  295. // A thumbnail of the asked dimensions should also have been created (within the constraints of the max preview)
  296. list($limitedPreviewWidth, $limitedPreviewHeight) =
  297. $this->simulatePreviewDimensions($previewWidth, $previewHeight);
  298. $actualWidth = $image->width();
  299. $actualHeight = $image->height();
  300. $this->assertEquals(
  301. (int)$limitedPreviewWidth, $image->width(), "$actualWidth x $actualHeight \n"
  302. );
  303. $this->assertEquals((int)$limitedPreviewHeight, $image->height());
  304. // And it should be cached
  305. $this->checkCache($sampleFileId, $limitedPreviewWidth, $limitedPreviewHeight);
  306. $preview->deleteAllPreviews();
  307. }
  308. /**
  309. * Tests if the second preview will be based off the cached max preview
  310. *
  311. * @requires extension imagick
  312. * @dataProvider dimensionsDataProvider
  313. *
  314. * @param int $sampleId
  315. * @param int $widthAdjustment
  316. * @param int $heightAdjustment
  317. * @param bool $keepAspect
  318. * @param bool $scalingUp
  319. */
  320. public function testSecondPreviewsGetCachedMax(
  321. $sampleId, $widthAdjustment, $heightAdjustment, $keepAspect = false, $scalingUp = false
  322. ) {
  323. //$this->markTestSkipped('Not testing this at this time');
  324. $this->getSample($sampleId);
  325. $sampleWidth = $this->sampleWidth;
  326. $sampleHeight = $this->sampleHeight;
  327. $sampleFileId = $this->sampleFileId;
  328. //Creates the Max preview which will be used in the rest of the test
  329. $this->createMaxPreview();
  330. // Adjust the requested size so that we trigger various test cases
  331. $previewWidth = $sampleWidth + $widthAdjustment;
  332. $previewHeight = $sampleHeight + $heightAdjustment;
  333. $this->keepAspect = $keepAspect;
  334. $this->scalingUp = $scalingUp;
  335. $preview = $this->createPreview($previewWidth, $previewHeight);
  336. // A cache query should return the thumbnail of max dimension
  337. $isCached = $preview->isCached($sampleFileId);
  338. $cachedMaxPreview = $this->buildCachePath(
  339. $sampleFileId, $this->maxPreviewWidth, $this->maxPreviewHeight, false, '-max'
  340. );
  341. $this->assertSame($cachedMaxPreview, $isCached);
  342. }
  343. /**
  344. * Make sure that the max preview can never be deleted
  345. *
  346. * For this test to work, the preview we generate first has to be the size of max preview
  347. */
  348. public function testMaxPreviewCannotBeDeleted() {
  349. //$this->markTestSkipped('Not testing this at this time');
  350. $this->keepAspect = true;
  351. $this->getSample(0);
  352. $fileId = $this->sampleFileId;
  353. //Creates the Max preview which we will try to delete
  354. $preview = $this->createMaxPreview();
  355. // We try to deleted the preview
  356. $preview->deletePreview();
  357. $this->assertNotSame(false, $preview->isCached($fileId));
  358. $preview->deleteAllPreviews();
  359. }
  360. public static function aspectDataProvider() {
  361. $data = [];
  362. $samples = 4;
  363. $keepAspect = false;
  364. $scalingUp = false;
  365. for ($a = 0; $a < $samples; $a++) {
  366. for ($b = 0; $b < 2; $b++) {
  367. for ($c = 0; $c < 2; $c++) {
  368. $row = [$a];
  369. $row[] = $keepAspect;
  370. $row[] = $scalingUp;
  371. $data[] = $row;
  372. $scalingUp = !$scalingUp;
  373. }
  374. $keepAspect = !$keepAspect;
  375. }
  376. }
  377. return $data;
  378. }
  379. /**
  380. * We ask for a preview larger than what is set in the configuration,
  381. * so we should be getting either the max preview or a preview the size
  382. * of the dimensions set in the config
  383. *
  384. * @requires extension imagick
  385. * @dataProvider aspectDataProvider
  386. *
  387. * @param int $sampleId
  388. * @param bool $keepAspect
  389. * @param bool $scalingUp
  390. */
  391. public function testDoNotCreatePreviewsLargerThanConfigMax(
  392. $sampleId, $keepAspect = false, $scalingUp = false
  393. ) {
  394. //$this->markTestSkipped('Not testing this at this time');
  395. $this->getSample($sampleId);
  396. //Creates the Max preview which will be used in the rest of the test
  397. $this->createMaxPreview();
  398. // Now we will create the real preview
  399. $previewWidth = 4000;
  400. $previewHeight = 4000;
  401. $this->keepAspect = $keepAspect;
  402. $this->scalingUp = $scalingUp;
  403. // Tries to create the very large preview
  404. $preview = $this->createPreview($previewWidth, $previewHeight);
  405. $image = $preview->getPreview();
  406. $this->assertNotSame(false, $image);
  407. list($expectedWidth, $expectedHeight) =
  408. $this->simulatePreviewDimensions($previewWidth, $previewHeight);
  409. $this->assertEquals($expectedWidth, $image->width());
  410. $this->assertEquals($expectedHeight, $image->height());
  411. // A preview of the asked size should not have been created since it's larger that our max dimensions
  412. $postfix = $this->getThumbnailPostfix($previewWidth, $previewHeight);
  413. $thumbCacheFile = $this->buildCachePath(
  414. $this->sampleFileId, $previewWidth, $previewHeight, false, $postfix
  415. );
  416. $this->assertSame(
  417. false, $this->rootView->file_exists($thumbCacheFile), "$thumbCacheFile \n"
  418. );
  419. $preview->deleteAllPreviews();
  420. }
  421. /**
  422. * Makes sure we're getting the proper cached thumbnail
  423. *
  424. * When we start by generating a preview which keeps the aspect ratio
  425. * 200-125-with-aspect
  426. * 300-300 ✓
  427. *
  428. * When we start by generating a preview of exact dimensions
  429. * 200-200 ✓
  430. * 300-188-with-aspect
  431. *
  432. * @requires extension imagick
  433. * @dataProvider aspectDataProvider
  434. *
  435. * @param int $sampleId
  436. * @param bool $keepAspect
  437. * @param bool $scalingUp
  438. */
  439. public function testIsBiggerWithAspectRatioCached(
  440. $sampleId, $keepAspect = false, $scalingUp = false
  441. ) {
  442. //$this->markTestSkipped('Not testing this at this time');
  443. $previewWidth = 400;
  444. $previewHeight = 400;
  445. $this->getSample($sampleId);
  446. $fileId = $this->sampleFileId;
  447. $this->keepAspect = $keepAspect;
  448. $this->scalingUp = $scalingUp;
  449. // Caching the max preview in our preview array for the test
  450. $this->cachedBigger[] = $this->buildCachePath(
  451. $fileId, $this->maxPreviewWidth, $this->maxPreviewHeight, false, '-max'
  452. );
  453. $this->getSmallerThanMaxPreview($fileId, $previewWidth, $previewHeight);
  454. // We switch the aspect ratio, to generate a thumbnail we should not be picked up
  455. $this->keepAspect = !$keepAspect;
  456. $this->getSmallerThanMaxPreview($fileId, $previewWidth + 100, $previewHeight + 100);
  457. // Small thumbnails are always cropped
  458. $this->keepAspect = false;
  459. // Smaller previews should be based on the previous, larger preview, with the correct aspect ratio
  460. $this->createThumbnailFromBiggerCachedPreview($fileId, 32, 32);
  461. // 2nd cache query should indicate that we have a cached copy of the exact dimension
  462. $this->getCachedSmallThumbnail($fileId, 32, 32);
  463. // We create a preview in order to be able to delete the cache
  464. $preview = $this->createPreview(rand(), rand());
  465. $preview->deleteAllPreviews();
  466. $this->cachedBigger = [];
  467. }
  468. /**
  469. * Initialises the preview
  470. *
  471. * @param int $width
  472. * @param int $height
  473. *
  474. * @return Preview
  475. */
  476. private function createPreview($width, $height) {
  477. $preview = new Preview(
  478. self::TEST_PREVIEW_USER1, 'files/', $this->sampleFilename, $width,
  479. $height
  480. );
  481. $this->assertSame(true, $preview->isFileValid());
  482. $preview->setKeepAspect($this->keepAspect);
  483. $preview->setScalingup($this->scalingUp);
  484. return $preview;
  485. }
  486. /**
  487. * Creates the Max preview which will be used in the rest of the test
  488. *
  489. * @return Preview
  490. */
  491. private function createMaxPreview() {
  492. $this->keepAspect = true;
  493. $preview = $this->createPreview($this->maxPreviewWidth, $this->maxPreviewHeight);
  494. $preview->getPreview();
  495. return $preview;
  496. }
  497. /**
  498. * Makes sure the preview which was just created has been saved to disk
  499. *
  500. * @param int $fileId
  501. * @param int $previewWidth
  502. * @param int $previewHeight
  503. */
  504. private function checkCache($fileId, $previewWidth, $previewHeight) {
  505. $postfix = $this->getThumbnailPostfix($previewWidth, $previewHeight);
  506. $thumbCacheFile = $this->buildCachePath(
  507. $fileId, $previewWidth, $previewHeight, true, $postfix
  508. );
  509. $this->assertSame(
  510. true, $this->rootView->file_exists($thumbCacheFile), "$thumbCacheFile \n"
  511. );
  512. }
  513. /**
  514. * Computes special filename postfixes
  515. *
  516. * @param int $width
  517. * @param int $height
  518. *
  519. * @return string
  520. */
  521. private function getThumbnailPostfix($width, $height) {
  522. // Need to take care of special postfix added to the dimensions
  523. $postfix = '';
  524. $isMaxPreview = ($width === $this->maxPreviewWidth
  525. && $height === $this->maxPreviewHeight) ? true : false;
  526. if ($isMaxPreview) {
  527. $postfix = '-max';
  528. }
  529. if ($this->keepAspect && !$isMaxPreview) {
  530. $postfix = '-with-aspect';
  531. }
  532. return $postfix;
  533. }
  534. private function getSmallerThanMaxPreview($fileId, $previewWidth, $previewHeight) {
  535. $preview = $this->createPreview($previewWidth, $previewHeight);
  536. $image = $preview->getPreview();
  537. $this->assertNotSame(false, $image);
  538. // A thumbnail of the asked dimensions should also have been created (within the constraints of the max preview)
  539. list($limitedPreviewWidth, $limitedPreviewHeight) =
  540. $this->simulatePreviewDimensions($previewWidth, $previewHeight);
  541. $this->assertEquals($limitedPreviewWidth, $image->width());
  542. $this->assertEquals($limitedPreviewHeight, $image->height());
  543. // And it should be cached
  544. $this->checkCache($fileId, $limitedPreviewWidth, $limitedPreviewHeight);
  545. $this->cachedBigger[] = $preview->isCached($fileId);
  546. }
  547. private function createThumbnailFromBiggerCachedPreview($fileId, $width, $height) {
  548. $preview = $this->createPreview($width, $height);
  549. // A cache query should return a thumbnail of slightly larger dimensions
  550. // and with the proper aspect ratio
  551. $isCached = $preview->isCached($fileId);
  552. $expectedCachedBigger = $this->getExpectedCachedBigger();
  553. $this->assertSame($expectedCachedBigger, $isCached);
  554. $image = $preview->getPreview();
  555. $this->assertNotSame(false, $image);
  556. }
  557. /**
  558. * Picks the bigger cached preview with the correct aspect ratio or the max preview if it's
  559. * smaller than that
  560. *
  561. * For non-upscaled images, we pick the only picture without aspect ratio
  562. *
  563. * @return string
  564. */
  565. private function getExpectedCachedBigger() {
  566. $foundPreview = null;
  567. $foundWidth = null;
  568. $foundHeight = null;
  569. $maxPreview = null;
  570. $maxWidth = null;
  571. $maxHeight = null;
  572. foreach ($this->cachedBigger as $cached) {
  573. $size = explode('-', basename($cached));
  574. $width = (int)$size[0];
  575. $height = (int)$size[1];
  576. if (strpos($cached, 'max')) {
  577. $maxWidth = $width;
  578. $maxHeight = $height;
  579. $maxPreview = $cached;
  580. continue;
  581. }
  582. // We pick the larger preview with no aspect ratio
  583. if (!strpos($cached, 'aspect') && !strpos($cached, 'max')) {
  584. $foundPreview = $cached;
  585. $foundWidth = $width;
  586. $foundHeight = $height;
  587. }
  588. }
  589. if ($foundWidth > $maxWidth && $foundHeight > $maxHeight) {
  590. $foundPreview = $maxPreview;
  591. }
  592. return $foundPreview;
  593. }
  594. /**
  595. * A small thumbnail of exact dimensions should be in the cache
  596. *
  597. * @param int $fileId
  598. * @param int $width
  599. * @param int $height
  600. */
  601. private function getCachedSmallThumbnail($fileId, $width, $height) {
  602. $preview = $this->createPreview($width, $height);
  603. $isCached = $preview->isCached($fileId);
  604. $thumbCacheFile = $this->buildCachePath($fileId, $width, $height);
  605. $this->assertSame($thumbCacheFile, $isCached, "$thumbCacheFile \n");
  606. }
  607. /**
  608. * Builds the complete path to a cached thumbnail starting from the user folder
  609. *
  610. * @param int $fileId
  611. * @param int $width
  612. * @param int $height
  613. * @param bool $user
  614. * @param string $postfix
  615. *
  616. * @return string
  617. */
  618. private function buildCachePath($fileId, $width, $height, $user = false, $postfix = '') {
  619. $userPath = '';
  620. if ($user) {
  621. $userPath = '/' . self::TEST_PREVIEW_USER1 . '/';
  622. }
  623. return $userPath . Preview::THUMBNAILS_FOLDER . '/' . $fileId
  624. . '/' . $width . '-' . $height . $postfix . '.png';
  625. }
  626. /**
  627. * Stores the sample in the filesystem and stores it in the $samples array
  628. *
  629. * @param string $fileName
  630. * @param int $sampleWidth
  631. * @param int $sampleHeight
  632. */
  633. private function prepareSample($fileName, $sampleWidth, $sampleHeight) {
  634. $imgData = file_get_contents(\OC::$SERVERROOT . '/tests/data/' . $fileName);
  635. $imgPath = '/' . self::TEST_PREVIEW_USER1 . '/files/' . $fileName;
  636. $this->rootView->file_put_contents($imgPath, $imgData);
  637. $fileInfo = $this->rootView->getFileInfo($imgPath);
  638. list($maxPreviewWidth, $maxPreviewHeight) =
  639. $this->setMaxPreview($sampleWidth, $sampleHeight);
  640. $this->samples[] =
  641. [
  642. 'sampleFileId' => $fileInfo['fileid'],
  643. 'sampleFileName' => $fileName,
  644. 'sampleWidth' => $sampleWidth,
  645. 'sampleHeight' => $sampleHeight,
  646. 'maxPreviewWidth' => $maxPreviewWidth,
  647. 'maxPreviewHeight' => $maxPreviewHeight
  648. ];
  649. }
  650. /**
  651. * Sets the variables used to define the boundaries which need to be respected when using a
  652. * specific sample
  653. *
  654. * @param $sampleId
  655. */
  656. private function getSample($sampleId) {
  657. // Corrects a rounding difference when using the EPS (Imagick converted) sample
  658. $filename = $this->samples[$sampleId]['sampleFileName'];
  659. $splitFileName = pathinfo($filename);
  660. $extension = $splitFileName['extension'];
  661. $correction = ($extension === 'eps' && PHP_MAJOR_VERSION < 7) ? 1 : 0;
  662. $maxPreviewHeight = $this->samples[$sampleId]['maxPreviewHeight'];
  663. $maxPreviewHeight = $maxPreviewHeight - $correction;
  664. $this->sampleFileId = $this->samples[$sampleId]['sampleFileId'];
  665. $this->sampleFilename = $this->samples[$sampleId]['sampleFileName'];
  666. $this->sampleWidth = $this->samples[$sampleId]['sampleWidth'];
  667. $this->sampleHeight = $this->samples[$sampleId]['sampleHeight'];
  668. $this->maxPreviewWidth = $this->samples[$sampleId]['maxPreviewWidth'];
  669. $this->maxPreviewHeight = $maxPreviewHeight;
  670. $ratio = $this->maxPreviewWidth / $this->maxPreviewHeight;
  671. $this->maxPreviewRatio = $ratio;
  672. }
  673. /**
  674. * Defines the size of the max preview
  675. *
  676. * @fixme the Imagick previews don't have the exact same size on disk as they're calculated here
  677. *
  678. * @param int $sampleWidth
  679. * @param int $sampleHeight
  680. *
  681. * @return array
  682. */
  683. private function setMaxPreview($sampleWidth, $sampleHeight) {
  684. // Max previews are never scaled up
  685. $this->scalingUp = false;
  686. // Max previews always keep the aspect ratio
  687. $this->keepAspect = true;
  688. // We set this variable in order to be able to calculate the max preview with the proper aspect ratio
  689. $this->maxPreviewRatio = $sampleWidth / $sampleHeight;
  690. $maxPreviewWidth = min($sampleWidth, $this->configMaxWidth);
  691. $maxPreviewHeight = min($sampleHeight, $this->configMaxHeight);
  692. list($maxPreviewWidth, $maxPreviewHeight) =
  693. $this->applyAspectRatio($maxPreviewWidth, $maxPreviewHeight);
  694. return [$maxPreviewWidth, $maxPreviewHeight];
  695. }
  696. /**
  697. * Calculates the expected dimensions of the preview to be able to assess if we've got the
  698. * right result
  699. *
  700. * @param int $askedWidth
  701. * @param int $askedHeight
  702. *
  703. * @return array
  704. */
  705. private function simulatePreviewDimensions($askedWidth, $askedHeight) {
  706. $askedWidth = min($askedWidth, $this->configMaxWidth);
  707. $askedHeight = min($askedHeight, $this->configMaxHeight);
  708. if ($this->keepAspect) {
  709. // Defines the box in which the preview has to fit
  710. $scaleFactor = $this->scalingUp ? $this->maxScaleFactor : 1;
  711. $newPreviewWidth = min($askedWidth, $this->maxPreviewWidth * $scaleFactor);
  712. $newPreviewHeight = min($askedHeight, $this->maxPreviewHeight * $scaleFactor);
  713. list($newPreviewWidth, $newPreviewHeight) =
  714. $this->applyAspectRatio($newPreviewWidth, $newPreviewHeight);
  715. } else {
  716. list($newPreviewWidth, $newPreviewHeight) =
  717. $this->fixSize($askedWidth, $askedHeight);
  718. }
  719. return [(int)$newPreviewWidth, (int)$newPreviewHeight];
  720. }
  721. /**
  722. * Resizes the boundaries to match the aspect ratio
  723. *
  724. * @param int $askedWidth
  725. * @param int $askedHeight
  726. *
  727. * @return \int[]
  728. */
  729. private function applyAspectRatio($askedWidth, $askedHeight) {
  730. $originalRatio = $this->maxPreviewRatio;
  731. if ($askedWidth / $originalRatio < $askedHeight) {
  732. $askedHeight = round($askedWidth / $originalRatio);
  733. } else {
  734. $askedWidth = round($askedHeight * $originalRatio);
  735. }
  736. return [(int)$askedWidth, (int)$askedHeight];
  737. }
  738. /**
  739. * Clips or stretches the dimensions so that they fit in the boundaries
  740. *
  741. * @param int $askedWidth
  742. * @param int $askedHeight
  743. *
  744. * @return array
  745. */
  746. private function fixSize($askedWidth, $askedHeight) {
  747. if ($this->scalingUp) {
  748. $askedWidth = min($this->configMaxWidth, $askedWidth);
  749. $askedHeight = min($this->configMaxHeight, $askedHeight);
  750. }
  751. return [(int)$askedWidth, (int)$askedHeight];
  752. }
  753. public function testKeepAspectRatio() {
  754. $originalWidth = 1680;
  755. $originalHeight = 1050;
  756. $originalAspectRation = $originalWidth / $originalHeight;
  757. $preview = new Preview(
  758. self::TEST_PREVIEW_USER1, 'files/', 'testimage.jpg',
  759. 150,
  760. 150
  761. );
  762. $preview->setKeepAspect(true);
  763. $image = $preview->getPreview();
  764. $aspectRatio = $image->width() / $image->height();
  765. $this->assertEquals(round($originalAspectRation, 2), round($aspectRatio, 2));
  766. $this->assertLessThanOrEqual(150, $image->width());
  767. $this->assertLessThanOrEqual(150, $image->height());
  768. }
  769. public function testKeepAspectRatioCover() {
  770. $originalWidth = 1680;
  771. $originalHeight = 1050;
  772. $originalAspectRation = $originalWidth / $originalHeight;
  773. $preview = new Preview(
  774. self::TEST_PREVIEW_USER1, 'files/', 'testimage.jpg',
  775. 150,
  776. 150
  777. );
  778. $preview->setKeepAspect(true);
  779. $preview->setMode(Preview::MODE_COVER);
  780. $image = $preview->getPreview();
  781. $aspectRatio = $image->width() / $image->height();
  782. $this->assertEquals(round($originalAspectRation, 2), round($aspectRatio, 2));
  783. $this->assertGreaterThanOrEqual(150, $image->width());
  784. $this->assertGreaterThanOrEqual(150, $image->height());
  785. }
  786. public function testSetFileWithInfo() {
  787. $info = new FileInfo('/foo', null, '/foo', ['mimetype' => 'foo/bar'], null);
  788. $preview = new Preview();
  789. $preview->setFile('/foo', $info);
  790. $this->assertEquals($info, $this->invokePrivate($preview, 'getFileInfo'));
  791. }
  792. public function testIsCached() {
  793. $sourceFile = __DIR__ . '/../data/testimage.png';
  794. $userId = $this->getUniqueID();
  795. $this->createUser($userId, 'pass');
  796. $storage = new Temporary();
  797. $storage->mkdir('files');
  798. $this->registerMount($userId, $storage, '/' . $userId);
  799. \OC_Util::tearDownFS();
  800. \OC_Util::setupFS($userId);
  801. $preview = new Preview($userId, 'files');
  802. $view = new View('/' . $userId . '/files');
  803. $view->file_put_contents('test.png', file_get_contents($sourceFile));
  804. $info = $view->getFileInfo('test.png');
  805. $preview->setFile('test.png', $info);
  806. $preview->setMaxX(64);
  807. $preview->setMaxY(64);
  808. $this->assertFalse($preview->isCached($info->getId()));
  809. $preview->getPreview();
  810. $this->assertEquals('thumbnails/' . $info->getId() . '/64-64.png', $preview->isCached($info->getId()));
  811. }
  812. }