-
Notifications
You must be signed in to change notification settings - Fork 0
/
ResponsiveImages.php
280 lines (256 loc) · 9.77 KB
/
ResponsiveImages.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
<?php
declare(strict_types=1);
namespace JoseBa;
use Exception;
/**
* Módulo para generar imagenes responsive.
*/
class ResponsiveImages
{
const DEFAULT_STEP_SIZE = 200;
/**
* @var string
*/
private $thumbsFolderUrl;
/**
* @var string
*/
private $thumbsFolderPath;
/**
* @var int
*/
private $sizeStepInPx; // (px)
protected $thumbs = [];
/**
* @param string $thumbsFolderUrl Url desde la que será accesible la carpeta en la que se generan las miniaturas.
* @param string $thumbsFolderPath Ruta a la carpeta de las miniaturas.
* @param int $sizeStepInPx Tamaño en px que tendrá que haber de diferencia entre un tamaño el inmediatamente
* inferior para que se genere una nueva miniatura (si es 100, se harían imágenes de
* 100px, 200px, 3000px...)
*/
public function __construct(
string $thumbsFolderUrl,
string $thumbsFolderPath,
$sizeStepInPx = self::DEFAULT_STEP_SIZE
) {
$this->thumbsFolderUrl = $thumbsFolderUrl;
$this->thumbsFolderPath = realpath($thumbsFolderPath) . '/';
$this->sizeStepInPx = $sizeStepInPx;
}
/**
* Función que genera la etiqueta <img> con todo lo necesario para usar las imágenes responsive.
* @param string $image Ruta o url de la imagen que se quiere insertar.
* @param array $sizes Array que se usará para generar el parámetro "sizes" de la imagen responsive. En el array,
* la clave es el ancho máximo del navegador, y el valor el % del ancho del navegador que ocupa
* la imagen hasta ese ancho máximo. Si la clave es "default", el valor será el % del ancho de
* la imagen que se usará si no se encuentra dentro de las otras opciones. Es decir, para este
* array:
* [
* 500 => 50
* 980 => 33
* 'default' => 100
* ]
* Estamos indicando que por debajo de 500px, la imagen ocupa el 50% del ancho (50vw), por
* debajo de 980px, el 33%, y de ahí para arriba, el 100%.
*
* Es un poco lioso, pero es como funciona la etiqueta img con imágenes responsive.
* @param string $alt (opcional) Texto alt de la imagen.
* @param int $sizeStepInPx (opcional) Tamaño del "paso" en px que se usará para generar la imagen (por defecto 200)
* @return string Devuelve la etiqueta img lista para insertarla en el html.
* @throws Exception
*/
public function generateImg(string $image, $sizes, $alt = '', $sizeStepInPx = self::DEFAULT_STEP_SIZE)
{
if (empty($sizes)) {
throw new \Exception("El array sizes no puede estar vacío");
}
$output = '<img alt="' . $alt . '" ';
$this->thumbs = [];
$imagePath = $this->downloadImage($image);
$this->generateThumbs($imagePath, $sizeStepInPx);
$sizes = $this->generateSizesParameter($sizes);
$srcset = $this->generateSources();
$output .= $sizes . $srcset . ' >';
return $output;
}
/**
* Descarga la imagen si lo que llega es una URL.
* @param string $image Ruta a la imagen o URL.
* @return string Ruta a la imagen
*/
private function downloadImage(string $image)
{
if (!strpos($image, 'http') === 0) {
return $image;
}
// Es una URL, nos bajamos la imagen y devolvemos la ruta
$imageContents = file_get_contents($image);
$imageUrlParts = explode('/', $image);
$imageFileName = end($imageUrlParts);
$thumbFolder = $this->getImageFolder($image);
$outputPath = $thumbFolder . '/' . $imageFileName;
file_put_contents($outputPath, $imageContents);
return $outputPath;
}
/**
* Genera el directorio donde se guardará la imagen
* @param string $imagePath Ruta completa o URL de la imagen
* @return string
*/
private function getImageFolder(string $imagePath)
{
$pathHash = md5($imagePath);
$firstLevel = substr($pathHash, 0, 2);
$secondLevel = substr($pathHash, 2, 2);
$firstLevelPath = rtrim($this->thumbsFolderPath, '/') . '/' . $firstLevel;
$secondLevelPath = $firstLevelPath . '/' . $secondLevel;
if (!is_dir($firstLevelPath)) {
mkdir($firstLevelPath);
}
if (!is_dir($secondLevelPath)) {
mkdir($secondLevelPath);
}
return $secondLevelPath;
}
/**
* Genera los archivos de las miniaturas.
* @param string $imagePath Ruta de la imagen
* @param int $sizeStepInPx Tamaño del paso
* @throws Exception
*/
private function generateThumbs(string $imagePath, int $sizeStepInPx)
{
$imageSize = getimagesize($imagePath);
$width = $imageSize[0];
$height = $imageSize[1];
$thumbSizes = $this->getThumbSizes($width, $height, $sizeStepInPx);
foreach ($thumbSizes as $size) {
$this->createThumbFile($imagePath, $size);
}
}
/**
* Calcula los tamaños necesarios de las miniaturas, a partir del tamaño de la imagen original y el tamaño deseado
* para el paso entre un thumb y el siguiente.
* @param $width
* @param $height
* @param int $sizeStepInPx
* @return array Tamaños de las miniaturas a generar.
*/
private function getThumbSizes($width, $height, int $sizeStepInPx)
{
$output = [[$width, $height]];
$newWidth = $width;
$newHeight = $height;
while ($newWidth > 0) {
$newHeight = $this->calculateNewHeight($newWidth, $newHeight, $sizeStepInPx);
$newWidth = $newWidth - $sizeStepInPx;
if ($newWidth > 0 AND $newHeight > 0) {
$output[] = [$newWidth, $newHeight];
}
}
return $output;
}
/**
* Calcula la altura de la siguiente miniatura, a partir de las dimensiones del tamaño de la actual.
* @param int $currentWidth Ancho de la imagen actual (última miniatura, por ejemplo).
* @param int $currentHeight Alto de la imagen actual.
* @param int $sizeStepInPx
* @return int Nueva altura
*/
private function calculateNewHeight($currentWidth, $currentHeight, int $sizeStepInPx)
{
$aspectRatio = $currentWidth / $currentHeight;
$newWidth = $currentWidth - $sizeStepInPx;
$newHeight = (int)floor($newWidth / $aspectRatio);
return $newHeight;
}
/**
* @param string $imagePath
* @param $size
* @throws Exception
*/
private function createThumbFile(string $imagePath, $size)
{
$originalImagePath = $imagePath;
$imagePath = realpath($imagePath);
$fileInfo = getimagesize($imagePath);
$imagePath = $this->generateThumbPath($imagePath, $size);
$this->thumbs[] = [
'path' => $imagePath,
'width' => $size[0],
'height' => $size[1]
];
if (file_exists($imagePath)) {
return;
}
$imageFunction = null;
switch ($fileInfo[2]) {
case IMAGETYPE_GIF:
$imageFunction = "ImageGIF";
$imageCreateFromFunction = "ImageCreateFromGIF";
break;
case IMAGETYPE_JPEG:
$imageFunction = "ImageJPEG";
$imageCreateFromFunction = "ImageCreateFromJPEG";
break;
case IMAGETYPE_PNG:
$imageFunction = "ImagePNG";
$imageCreateFromFunction = "ImageCreateFromPNG";
break;
default:
throw new Exception("Tipo de imagen desconocido");
}
if ($imageFunction) {
$originalImage = $imageCreateFromFunction($originalImagePath);
$thumb = imagecreatetruecolor($size[0], $size[1]);
imagecopyresized($thumb, $originalImage, 0, 0, 0, 0, $size[0], $size[1], $fileInfo[0],
$fileInfo[1]);
$imageFunction($thumb, $imagePath);
}
}
/**
* Crea la ruta de la miniatura.
* @param string $imagePath
* @param $size
* @return mixed
*/
private function generateThumbPath(string $imagePath, $size)
{
$originalFileName = basename($imagePath);
$fileNameComponents = explode('.', $originalFileName);
$newFilename = $fileNameComponents[0] . '_' . $size[0] . 'x' . $size[1] . '.' . $fileNameComponents[1];
return str_replace($originalFileName, $newFilename, $imagePath);
}
/**
* Genera el atributo "sizes" de la etiqueta img responsive.
* @param $sizes
* @return string
*/
private function generateSizesParameter($sizes)
{
$output = " sizes='";
foreach ($sizes as $maxWidth => $imageWidth) {
$output .= "(max-width: {$maxWidth}px) {$imageWidth}vw,\n";
}
$defaultWidth = isset($sizes['default']) ? $sizes['default'] . 'px' : '100vw';
$output .= "{$defaultWidth}' ";
return $output;
}
/**
* Genera el atributo "sources" de la etiqueta img responsive.
* @return bool|string
*/
private function generateSources()
{
// Necesito generar las urls, y luego ya las filas con los tamaños
$output = " srcset='";
$thumbs = array_reverse($this->thumbs);
foreach ($thumbs as $thumb) {
$thumbUrl = str_replace($this->thumbsFolderPath, $this->thumbsFolderUrl, $thumb['path']);
$output .= $thumbUrl . " {$thumb['width']}w,\n";
}
$output = substr($output, 0, -2);
$output .= "' ";
return $output;
}
}