preview.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\Storage\Temporary;
  25. use OC\Files\View;
  26. use Test\Traits\MountProviderTrait;
  27. use Test\Traits\UserTrait;
  28. /**
  29. * Class Preview
  30. *
  31. * @group DB
  32. *
  33. * @package Test
  34. */
  35. class Preview 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. }