You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

963 lines
28 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
  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. }