Completed
Push — stable8.2 ( 09e830...ae9bfd )
by Olivier
12:09
created

Preview::fixPreviewCache()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 11
cts 11
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 12
nc 2
nop 1
crap 2
1
<?php
2
/**
3
 * ownCloud - galleryplus
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the COPYING file.
7
 *
8
 * @author Olivier Paroz <[email protected]>
9
 *
10
 * @copyright Olivier Paroz 2014-2015
11
 */
12
13
namespace OCA\GalleryPlus\Preview;
14
15
use OCP\IConfig;
16
use OCP\Image;
17
use OCP\Files\File;
18
use OCP\IPreview;
19
use OCP\ILogger;
20
21
/**
22
 * Generates previews
23
 *
24
 * @todo On OC8.2, replace \OC\Preview with IPreview
25
 *
26
 * @package OCA\GalleryPlus\Preview
27
 */
28
class Preview {
29
30
	/**
31
	 * @var string
32
	 */
33
	private $dataDir;
34
	/**
35
	 * @var mixed
36
	 */
37
	private $previewManager;
38
	/**
39
	 * @var ILogger
40
	 */
41
	private $logger;
42
	/**
43
	 * @var string
44
	 */
45
	private $userId;
46
	/**
47
	 * @var \OC\Preview
48
	 */
49
	private $preview;
50
	/**
51
	 * @var File
52
	 */
53
	private $file;
54
	/**
55
	 * @var int[]
56
	 */
57
	private $dims;
58
59
	/**
60
	 * Constructor
61
	 *
62
	 * @param IConfig $config
63
	 * @param IPreview $previewManager
64
	 * @param ILogger $logger
65
	 */
66 43
	public function __construct(
67
		IConfig $config,
68
		IPreview $previewManager,
69
		ILogger $logger
70
	) {
71 43
		$this->dataDir = $config->getSystemValue('datadirectory');
72 43
		$this->previewManager = $previewManager;
73 43
		$this->logger = $logger;
74 43
	}
75
76
	/**
77
	 * Returns true if the passed mime type is supported
78
	 *
79
	 * @param string $mimeType
80
	 *
81
	 * @return boolean
82
	 */
83 16
	public function isMimeSupported($mimeType = '*') {
84 16
		return $this->previewManager->isMimeSupported($mimeType);
85
	}
86
87
	/**
88
	 * Initialises the view which will be used to access files and generate previews
89
	 *
90
	 * @fixme Private API, but can't use the PreviewManager yet as it's incomplete
91
	 *
92
	 * @param string $userId
93
	 * @param File $file
94
	 * @param string $imagePathFromFolder
95
	 */
96 7
	public function setupView($userId, $file, $imagePathFromFolder) {
97 7
		$this->userId = $userId;
98 7
		$this->file = $file;
99 7
		$imagePathFromFolder = ltrim($imagePathFromFolder, '/');
100 7
		$this->preview = new \OC\Preview($userId, 'files', $imagePathFromFolder);
101 7
	}
102
103
	/**
104
	 * Returns a preview based on OC's preview class and our custom methods
105
	 *
106
	 * We check that the preview returned by the Preview class can be used by
107
	 * the browser. If not, we send "false" to the controller
108
	 *
109
	 * @fixme setKeepAspect is missing from public interface.
110
	 *     https://github.com/owncloud/core/issues/12772
111
	 *
112
	 * @param int $maxWidth
113
	 * @param int $maxHeight
114
	 * @param bool $keepAspect
115
	 *
116
	 * @return array<string,string|\OC_Image>|false
117
	 */
118 7
	public function preparePreview($maxWidth, $maxHeight, $keepAspect) {
119 7
		$this->dims = [$maxWidth, $maxHeight];
120
121 7
		$previewData = $this->getPreviewFromCore($keepAspect);
122
123 7
		if ($previewData && $previewData->valid()) {
124
			$preview = [
125 5
				'preview'  => $previewData,
126 5
				'mimetype' => $previewData->mimeType()
127
			];
128
		} else {
129 2
			$preview = false;
130
		}
131
132 7
		return $preview;
133
	}
134
135
	/**
136
	 * Makes sure we return previews of the asked dimensions and fix the cache
137
	 * if necessary
138
	 *
139
	 * The Preview class sometimes return previews which are either wider or
140
	 * smaller than the asked dimensions. This happens when one of the original
141
	 * dimension is smaller than what is asked for
142
	 *
143
	 * For square previews, we also need to make sure the entire surface is filled in order to make
144
	 * it easier to work with when building albums
145
	 *
146
	 * @param bool $square
147
	 *
148
	 * @return \OC_Image
149
	 */
150 12
	public function previewValidator($square) {
151 12
		list($maxWidth, $maxHeight) = $this->dims;
152 12
		$previewData = $this->preview->getPreview();
153 12
		$previewWidth = $previewData->width();
154 12
		$previewHeight = $previewData->height();
155
156 12
		if ($previewWidth > $maxWidth || $previewHeight > $maxHeight) {
157 7
			$previewData = $this->fixPreview(
158
				$previewData, $previewWidth, $previewHeight, $maxWidth, $maxHeight, $square
159
			);
160
		}
161
162 12
		return $previewData;
163
	}
164
165
	/**
166
	 * Asks core for a preview based on our criteria
167
	 *
168
	 * @todo Need to read scaling setting from settings
169
	 *
170
	 * @param bool $keepAspect
171
	 *
172
	 * @return \OC_Image
173
	 */
174 8
	private function getPreviewFromCore($keepAspect) {
175 8
		list($maxX, $maxY) = $this->dims;
176
177 8
		$this->preview->setMaxX($maxX);
178 8
		$this->preview->setMaxY($maxY);
179 8
		$this->preview->setScalingUp(false);
180 8
		$this->preview->setKeepAspect($keepAspect);
181
182
		//$this->logger->debug("[PreviewService] preview {preview}", ['preview' => $this->preview]);
183
184 8
		$previewData = $this->preview->getPreview();
185
186 7
		return $previewData;
187
	}
188
189
	/**
190
	 * Makes a preview fit in the asked dimension and, if required, fills the empty space
191
	 *
192
	 * @param \OCP\IImage $previewData
193
	 * @param int $previewWidth
194
	 * @param int $previewHeight
195
	 * @param int $maxWidth
196
	 * @param int $maxHeight
197
	 * @param bool $square
198
	 *
199
	 * @return \OC_Image
200
	 */
201 7
	private function fixPreview(
202
		$previewData, $previewWidth, $previewHeight, $maxWidth, $maxHeight, $square
203
	) {
204
205 7
		if ($square || $previewWidth > $maxWidth || $previewHeight > $maxHeight) {
206 7
			$fixedPreview = $this->resize(
207
				$previewData, $previewWidth, $previewHeight, $maxWidth, $maxHeight, $square
208
			);
209 7
			$previewData = $this->fixPreviewCache($fixedPreview);
210
		}
211
212 7
		return $previewData;
213
	}
214
215
	/**
216
	 * Makes a preview fit in the asked dimension and, if required, fills the empty space
217
	 *
218
	 * @param \OCP\IImage $previewData
219
	 * @param int $previewWidth
220
	 * @param int $previewHeight
221
	 * @param int $maxWidth
222
	 * @param int $maxHeight
223
	 * @param bool $fill
224
	 *
225
	 * @return resource
226
	 */
227 7
	private function resize(
228
		$previewData, $previewWidth, $previewHeight, $maxWidth, $maxHeight, $fill
229
	) {
230
		list($newX, $newY, $newWidth, $newHeight) =
231 7
			$this->calculateNewDimensions($previewWidth, $previewHeight, $maxWidth, $maxHeight);
232
233 7
		if (!$fill) {
234 4
			$newX = $newY = 0;
235 4
			$maxWidth = $newWidth;
236 4
			$maxHeight = $newHeight;
237
		}
238
239 7
		$resizedPreview = $this->processPreview(
240
			$previewData, $previewWidth, $previewHeight, $newWidth, $newHeight, $maxWidth,
241
			$maxHeight, $newX, $newY
242
		);
243
244 7
		return $resizedPreview;
245
	}
246
247
	/**
248
	 * Mixes a transparent background with a resized foreground preview
249
	 *
250
	 * @param \OCP\IImage $previewData
251
	 * @param int $previewWidth
252
	 * @param int $previewHeight
253
	 * @param int $newWidth
254
	 * @param int $newHeight
255
	 * @param int $maxWidth
256
	 * @param int $maxHeight
257
	 * @param int $newX
258
	 * @param int $newY
259
	 *
260
	 * @return resource
261
	 */
262 7
	private function processPreview(
263
		$previewData, $previewWidth, $previewHeight, $newWidth, $newHeight, $maxWidth, $maxHeight,
264
		$newX, $newY
265
	) {
266 7
		$fixedPreview = imagecreatetruecolor($maxWidth, $maxHeight); // Creates the canvas
267
268
		// We make the background transparent
269 7
		imagealphablending($fixedPreview, false);
270 7
		$transparency = imagecolorallocatealpha($fixedPreview, 0, 0, 0, 127);
271 7
		imagefill($fixedPreview, 0, 0, $transparency);
272 7
		imagesavealpha($fixedPreview, true);
273
274
275 7
		imagecopyresampled(
276 7
			$fixedPreview, $previewData->resource(),
277 7
			$newX, $newY, 0, 0, $newWidth, $newHeight, $previewWidth, $previewHeight
278
		);
279
280 7
		return $fixedPreview;
281
	}
282
283
	/**
284
	 * Calculates the new dimensions so that it fits in the dimensions requested by the client
285
	 *
286
	 * @link https://stackoverflow.com/questions/3050952/resize-an-image-and-fill-gaps-of-proportions-with-a-color
287
	 *
288
	 * @param int $previewWidth
289
	 * @param int $previewHeight
290
	 * @param int $maxWidth
291
	 * @param int $maxHeight
292
	 *
293
	 * @return array<int,double>
294
	 */
295 7
	private function calculateNewDimensions($previewWidth, $previewHeight, $maxWidth, $maxHeight) {
296 7
		if (($previewWidth / $previewHeight) >= ($maxWidth / $maxHeight)) {
297 4
			$newWidth = $maxWidth;
298 4
			$newHeight = $previewHeight * ($maxWidth / $previewWidth);
299 4
			$newX = 0;
300 4
			$newY = round(abs($maxHeight - $newHeight) / 2);
301
		} else {
302 3
			$newWidth = $previewWidth * ($maxHeight / $previewHeight);
303 3
			$newHeight = $maxHeight;
304 3
			$newX = round(abs($maxWidth - $newWidth) / 2);
305 3
			$newY = 0;
306
		}
307
308 7
		return [$newX, $newY, $newWidth, $newHeight];
309
	}
310
311
	/**
312
	 * Fixes the preview cache by replacing the broken thumbnail with ours
313
	 *
314
	 * WARNING: Will break if the thumbnail folder ever moves or if encryption is turned on for
315
	 * thumbnails
316
	 *
317
	 * @param resource $fixedPreview
318
	 *
319
	 * @return \OCP\IImage
320
	 */
321 7
	private function fixPreviewCache($fixedPreview) {
322 7
		$owner = $this->userId;
323 7
		$file = $this->file;
324 7
		$preview = $this->preview;
325 7
		$fixedPreviewObject = new Image($fixedPreview);
326
		// Get the location where the broken thumbnail is stored
327 7
		$thumbnailFolder = $this->dataDir . '/' . $owner . '/';
328 7
		$thumbnail = $thumbnailFolder . $preview->isCached($file->getId());
329
330
		// Caching it for next time
331 7
		if ($fixedPreviewObject->save($thumbnail)) {
332 6
			$previewData = $fixedPreviewObject;
333
		} else {
334 1
			$previewData = $preview->getPreview();
335
		}
336
337 7
		return $previewData;
338
	}
339
340
}
341