Issues (8)

src/Deepzoom.php (7 issues)

1
<?php
2
3
namespace Jeremytubbs\Deepzoom;
4
5
use Intervention\Image\ImageManager;
6
use League\Flysystem\FilesystemOperator;
7
8
/**
9
 * Class Deepzoom
10
 * @package Jeremytubbs\Deepzoom
11
 */
12
class Deepzoom
13
{
14
    protected $tileFormat;
15
16
    private $tileSize;
17
    private $tileOverlap;
18
    private $pathPrefix;
19
20
    /**
21
     * @param FilesystemOperator $path
22
     * @param ImageManager $imageManager
23
     */
24
    public function __construct(FilesystemOperator $path, ImageManager $imageManager, $tileFormat, $pathPrefix)
25
    {
26
        $this->setImageManager($imageManager);
27
        $this->setPath($path);
28
        $this->tileSize = 256;
29
        $this->tileOverlap = 1;
30
        $this->tileFormat = $tileFormat;
31
        $this->pathPrefix = $pathPrefix;
32
    }
33
34
    /**
35
     * @param $image
36
     * @param null $file
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $file is correct as it would always require null to be passed?
Loading history...
37
     * @param null $folder
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $folder is correct as it would always require null to be passed?
Loading history...
38
     * @return array|string
39
     */
40
    public function makeTiles($image, $file = null, $folder = null)
41
    {
42
        // path to a test image
43
        $img = $this->imageManager->make($image);
44
45
        // get image width and height
46
        $height = $img->height();
47
        $width = $img->width();
48
49
        $maxDimension = max([$width, $height]);
50
51
        // calculate the number of levels
52
        $numLevels = $this->getNumLevels($maxDimension);
53
54
        // set filename or use path filename
55
        $filename = $file !== null ? $file : pathinfo($image)['filename'];
0 ignored issues
show
The condition $file !== null is always false.
Loading history...
56
        $filename = $this->cleanupFilename($filename);
57
58
        // set folder or use path filename
59
        $foldername = $folder !== null ? $folder : pathinfo($image)['filename'];
0 ignored issues
show
The condition $folder !== null is always false.
Loading history...
60
        $foldername = $this->cleanupFolderName($foldername);
61
62
        // check for spaces in names
63
        $check = $this->checkJsonFilename($filename);
64
        if ($check != 'ok') {
65
            return $check;
66
        }
67
68
        $folder = $foldername . '/' . $filename . '_files';
69
        $this->path->createDirectory($folder);
70
71
        foreach (range($numLevels - 1, 0) as $level) {
72
            $level_folder = $folder . '/' . $level;
73
            $this->path->createDirectory($level_folder);
74
            // calculate scale for level
75
            $scale = $this->getScaleForLevel($numLevels, $level);
76
            // calculate dimensions for levels
77
            $dimension = $this->getDimensionForLevel($width, $height, $scale);
78
            // create tiles for level
79
            $this->createLevelTiles($dimension['width'], $dimension['height'], $level_folder, $img);
80
        }
81
82
        $DZI = $this->createDZI($height, $width);
83
        $this->path->write($foldername . '/' . $filename . '.dzi', $DZI);
84
85
        $JSONP = $this->createJSONP($filename, $height, $width);
86
        $this->path->write($foldername . '/' . $filename . '.js', $JSONP);
87
88
        $data = [
89
            'output' => [
90
                'JSONP' => "$this->pathPrefix/$foldername/$filename.js",
91
                'DZI' => "$this->pathPrefix/$foldername/$filename.dzi",
92
                '_files' => "$this->pathPrefix/$foldername/" . $filename . "_files",
93
            ],
94
            'source' => $image,
95
        ];
96
97
        return [
98
            'status' => 'ok',
99
            'data' => $data,
100
            'message' => 'Everything is okay!',
101
        ];
102
    }
103
104
    /**
105
     * @param $maxDimension
106
     * @return int
107
     */
108
    public function getNumLevels($maxDimension)
109
    {
110
        return (int)ceil(log($maxDimension, 2)) + 1;
111
    }
112
113
    /**
114
     * @param $width
115
     * @param $height
116
     * @return array
117
     */
118
    public function getNumTiles($width, $height)
119
    {
120
        $columns = (int)ceil(floatval($width) / $this->tileSize);
121
        $rows = (int)ceil(floatval($height) / $this->tileSize);
122
123
        return ['columns' => $columns, 'rows' => $rows];
124
    }
125
126
    /**
127
     * @param $numLevels
128
     * @param $level
129
     * @return number
130
     */
131
    public function getScaleForLevel($numLevels, $level)
132
    {
133
        $maxLevel = $numLevels - 1;
134
135
        return pow(0.5, $maxLevel - $level);
136
    }
137
138
    /**
139
     * @param $width
140
     * @param $height
141
     * @param $scale
142
     * @return array
143
     */
144
    public function getDimensionForLevel($width, $height, $scale)
145
    {
146
        $width = (int)ceil($width * $scale);
147
        $height = (int)ceil($height * $scale);
148
149
        return ['width' => $width, 'height' => $height];
150
    }
151
152
    /**
153
     * @param $width
154
     * @param $height
155
     * @param $folder
156
     * @param $img
157
     */
158
    public function createLevelTiles($width, $height, $folder, $img)
159
    {
160
        // create new image at scaled dimensions
161
        $img = $img->resize($width, $height);
162
        // get column and row count for level
163
        $tiles = $this->getNumTiles($width, $height);
164
165
        foreach (range(0, $tiles['columns'] - 1) as $column) {
166
            foreach (range(0, $tiles['rows'] - 1) as $row) {
167
                $tileImg = clone $img;
168
                $tile_file = $column . '_' . $row . '.' . $this->tileFormat;
169
                $bounds = $this->getTileBounds($column, $row, $width, $height);
170
                $tileImg->crop($bounds['width'], $bounds['height'], $bounds['x'], $bounds['y']);
171
                $tileImg->encode($this->tileFormat);
172
                $this->path->write("$folder/$tile_file", $tileImg);
0 ignored issues
show
$tileImg of type object is incompatible with the type string expected by parameter $contents of League\Flysystem\FilesystemWriter::write(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

172
                $this->path->write("$folder/$tile_file", /** @scrutinizer ignore-type */ $tileImg);
Loading history...
173
                unset($tileImg);
174
            }
175
        }
176
    }
177
178
    /**
179
     * @param $column
180
     * @param $row
181
     * @return array
182
     */
183
    public function getTileBoundsPosition($column, $row)
184
    {
185
        $offsetX = $column == 0 ? 0 : $this->tileOverlap;
186
        $offsetY = $row == 0 ? 0 : $this->tileOverlap;
187
        $x = ($column * $this->tileSize) - $offsetX;
188
        $y = ($row * $this->tileSize) - $offsetY;
189
190
        return ['x' => $x, 'y' => $y];
191
    }
192
193
    /**
194
     * @param $column
195
     * @param $row
196
     * @param $w
197
     * @param $h
198
     * @return array
199
     */
200
    public function getTileBounds($column, $row, $w, $h)
201
    {
202
        $position = $this->getTileBoundsPosition($column, $row);
203
204
        $width = $this->tileSize + ($column == 0 ? 1 : 2) * $this->tileOverlap;
205
        $height = $this->tileSize + ($row == 0 ? 1 : 2) * $this->tileOverlap;
206
        $newWidth = min($width, $w - $position['x']);
207
        $newHeight = min($height, $h - $position['y']);
208
209
        return array_merge($position, ['width' => $newWidth, 'height' => $newHeight]);
210
    }
211
212
    /**
213
     * @param $height
214
     * @param $width
215
     * @return string
216
     */
217
    public function createDZI($height, $width)
218
    {
219
        return <<<EOF
220
<?xml version="1.0" encoding="UTF-8"?>
221
<Image xmlns="http://schemas.microsoft.com/deepzoom/2008"
222
       Format="$this->tileFormat"
223
       Overlap="$this->tileOverlap"
224
       TileSize="$this->tileSize" >
225
    <Size Height="$height"
226
          Width="$width" />
227
</Image>
228
EOF;
229
    }
230
231
    /**
232
     * @param $filename
233
     * @param $height
234
     * @param $width
235
     * @return string
236
     */
237
    public function createJSONP($filename, $height, $width)
238
    {
239
        return <<<EOF
240
$filename({
241
    Image: {
242
        xmlns: 'http://schemas.microsoft.com/deepzoom/2008',
243
        Format: '$this->tileFormat',
244
        Overlap: $this->tileOverlap,
245
        TileSize: $this->tileSize,
246
        Size: {
247
            Width: $width,
248
            Height: $height
249
        }
250
    }
251
});
252
EOF;
253
    }
254
255
    /**
256
     * @param $string
257
     * @return string
258
     */
259
    public function cleanupFilename($string)
260
    {
261
        // trim space
262
        $string = trim($string);
263
        // replace strings, dashes and whitespaces with underscore
264
        return str_replace(['/\s/', '-', ' '], '_', $string);
265
    }
266
267
    /**
268
     * @param $string
269
     * @return string
270
     */
271
    public function cleanupFolderName($string)
272
    {
273
        // trim space
274
        $string = trim($string);
275
        // replace strings and whitespaces with dash
276
        return str_replace(['/\s/', ' '], '-', $string);
277
    }
278
279
    /**
280
     * @param $string
281
     * @return array|string
282
     */
283
    public function checkJsonFilename($string)
284
    {
285
        // for JSONP filename cannot contain special characters
286
        $specialCharRegex = '/[\'^£%&*()}{@#~?><> ,|=+¬-]/';
287
        if (preg_match($specialCharRegex, $string)) {
288
            return [
289
                'status' => 'error',
290
                'message' => 'JSONP filename name must not contain special characters.',
291
            ];
292
        }
293
        // for JSONP filename cannot start with a number
294
        $stringFirstChar = substr($string, 0, 1);
295
        // if numeric add 'a' to begining of filename
296
        if (is_numeric($stringFirstChar)) {
297
            return [
298
                'status' => 'error',
299
                'message' => 'JSONP filenames must not start with a numeric value.',
300
            ];
301
        }
302
303
        return 'ok';
304
    }
305
306
    /**
307
     * @param ImageManager $imageManager
308
     */
309
    public function setImageManager(ImageManager $imageManager)
310
    {
311
        $this->imageManager = $imageManager;
0 ignored issues
show
Bug Best Practice introduced by
The property imageManager does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
312
    }
313
314
    /**
315
     * @param FilesystemOperator $path
316
     */
317
    public function setPath(FilesystemOperator $path)
318
    {
319
        $this->path = $path;
0 ignored issues
show
Bug Best Practice introduced by
The property path does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
320
    }
321
}
322