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

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