Completed
Pull Request — stable9 (#52)
by Olivier
09:56
created

Preview::processPreview()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 10
cts 10
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 12
nc 1
nop 9
crap 1

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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-2016
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 44
	public function __construct(
67
		IConfig $config,
68
		IPreview $previewManager,
69
		ILogger $logger
70
	) {
71 44
		$this->dataDir = $config->getSystemValue('datadirectory');
72 44
		$this->previewManager = $previewManager;
73 44
		$this->logger = $logger;
74 44
	}
75
76
	/**
77
	 * Returns true if the passed mime type is supported
78
	 *
79
	 * @param string $mimeType
80
	 *
81
	 * @return boolean
82
	 */
83 17
	public function isMimeSupported($mimeType = '*') {
84 17
		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