Skip to content

Commit 68500e1

Browse files
committed
Duplicate resizeMulti code into resize
1 parent b4a8e4b commit 68500e1

1 file changed

Lines changed: 179 additions & 1 deletion

File tree

src/Image.php

Lines changed: 179 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,185 @@ final class Image
6060
*/
6161
public static function resize(\Imagick $source, int $boxWidth, int $boxHeight, array $options = []) : \Imagick
6262
{
63-
$results = self::resizeMulti($source, [['width' => $boxWidth, 'height' => $boxHeight]], $options);
63+
$boxSizes = [['width' => $boxWidth, 'height' => $boxHeight]];
64+
$options += self::DEFAULT_OPTIONS;
65+
66+
//algorithm inspired from http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html
67+
//use of 2x2 binning is arguably the best quality one will get downsizing and is what lots of hardware does in
68+
//the photography field, while being reasonably fast. Upsizing is more subjective but you can't get much
69+
//better than bicubic which is what is used here.
70+
71+
$color = $options['color'];
72+
Util::ensure(true, is_string($color), InvalidArgumentException::class, ['$options["color"] was not a string']);
73+
74+
$upsize = $options['upsize'];
75+
Util::ensure(true, is_bool($upsize), InvalidArgumentException::class, ['$options["upsize"] was not a bool']);
76+
77+
$bestfit = $options['bestfit'];
78+
Util::ensure(true, is_bool($bestfit), InvalidArgumentException::class, ['$options["bestfit"] was not a bool']);
79+
80+
$blurBackground = $options['blurBackground'];
81+
Util::ensure(
82+
true,
83+
is_bool($blurBackground),
84+
InvalidArgumentException::class,
85+
['$options["blurBackground"] was not a bool']
86+
);
87+
88+
$blurValue = $options['blurValue'];
89+
Util::ensure(
90+
true,
91+
is_float($blurValue),
92+
InvalidArgumentException::class,
93+
['$options["blurValue"] was not a float']
94+
);
95+
$maxWidth = $options['maxWidth'];
96+
Util::ensure(true, is_int($maxWidth), InvalidArgumentException::class, ['$options["maxWidth"] was not an int']);
97+
98+
$maxHeight = $options['maxHeight'];
99+
Util::ensure(
100+
true,
101+
is_int($maxHeight),
102+
InvalidArgumentException::class,
103+
['$options["maxHeight"] was not an int']
104+
);
105+
106+
foreach ($boxSizes as $boxSizeKey => $boxSize) {
107+
if (!isset($boxSize['width']) || !is_int($boxSize['width'])) {
108+
throw new InvalidArgumentException('a width in a $boxSizes value was not an int');
109+
}
110+
111+
if (!isset($boxSize['height']) || !is_int($boxSize['height'])) {
112+
throw new InvalidArgumentException('a height in a $boxSizes value was not an int');
113+
}
114+
115+
if ($boxSize['width'] > $maxWidth || $boxSize['width'] <= 0) {
116+
throw new InvalidArgumentException('a $boxSizes width was not between 0 and $options["maxWidth"]');
117+
}
118+
119+
if ($boxSize['height'] > $maxHeight || $boxSize['height'] <= 0) {
120+
throw new InvalidArgumentException('a $boxSizes height was not between 0 and $options["maxHeight"]');
121+
}
122+
}
123+
124+
$results = [];
125+
$cloneCache = [];
126+
foreach ($boxSizes as $boxSizeKey => $boxSize) {
127+
$boxWidth = $boxSize['width'];
128+
$boxHeight = $boxSize['height'];
129+
130+
$clone = clone $source;
131+
132+
self::rotateImage($clone);
133+
134+
$width = $clone->getImageWidth();
135+
$height = $clone->getImageHeight();
136+
137+
//ratio over 1 is horizontal, under 1 is vertical
138+
$boxRatio = $boxWidth / $boxHeight;
139+
//height should be positive since I didnt find a way you could get zero into imagick
140+
$originalRatio = $width / $height;
141+
142+
$targetWidth = null;
143+
$targetHeight = null;
144+
$targetX = null;
145+
$targetY = null;
146+
if ($width < $boxWidth && $height < $boxHeight && !$upsize) {
147+
$targetWidth = $width;
148+
$targetHeight = $height;
149+
$targetX = ($boxWidth - $width) / 2;
150+
$targetY = ($boxHeight - $height) / 2;
151+
} else {
152+
//if box is more vertical than original
153+
if ($boxRatio < $originalRatio) {
154+
$targetWidth = $boxWidth;
155+
$targetHeight = (int)((double)$boxWidth / $originalRatio);
156+
$targetX = 0;
157+
$targetY = ($boxHeight - $targetHeight) / 2;
158+
} else {
159+
$targetWidth = (int)((double)$boxHeight * $originalRatio);
160+
$targetHeight = $boxHeight;
161+
$targetX = ($boxWidth - $targetWidth) / 2;
162+
$targetY = 0;
163+
}
164+
}
165+
166+
//do iterative downsize by halfs (2x2 binning is a common name) on dimensions that are bigger than target
167+
//width and height
168+
while (true) {
169+
$widthReduced = false;
170+
$widthIsHalf = false;
171+
if ($width > $targetWidth) {
172+
$width = (int)($width / 2);
173+
$widthReduced = true;
174+
$widthIsHalf = true;
175+
if ($width < $targetWidth) {
176+
$width = $targetWidth;
177+
$widthIsHalf = false;
178+
}
179+
}
180+
181+
$heightReduced = false;
182+
$heightIsHalf = false;
183+
if ($height > $targetHeight) {
184+
$height = (int)($height / 2);
185+
$heightReduced = true;
186+
$heightIsHalf = true;
187+
if ($height < $targetHeight) {
188+
$height = $targetHeight;
189+
$heightIsHalf = false;
190+
}
191+
}
192+
193+
if (!$widthReduced && !$heightReduced) {
194+
break;
195+
}
196+
197+
$cacheKey = "{$width}x{$height}";
198+
if (isset($cloneCache[$cacheKey])) {
199+
$clone = clone $cloneCache[$cacheKey];
200+
continue;
201+
}
202+
203+
if ($clone->resizeImage($width, $height, \Imagick::FILTER_BOX, 1.0) !== true) {
204+
//cumbersome to test
205+
throw new \Exception('Imagick::resizeImage() did not return true');//@codeCoverageIgnore
206+
}
207+
208+
if ($widthIsHalf && $heightIsHalf) {
209+
$cloneCache[$cacheKey] = clone $clone;
210+
}
211+
}
212+
213+
if ($upsize && ($width < $targetWidth || $height < $targetHeight)) {
214+
if ($clone->resizeImage($targetWidth, $targetHeight, \Imagick::FILTER_CUBIC, 1.0, $bestfit) !== true) {
215+
//cumbersome to test
216+
throw new \Exception('Imagick::resizeImage() did not return true');//@codeCoverageIgnore
217+
}
218+
}
219+
220+
if ($clone->getImageHeight() === $boxHeight && $clone->getImageWidth() === $boxWidth) {
221+
$results[$boxSizeKey] = $clone;
222+
continue;
223+
}
224+
225+
//put image in box
226+
$canvas = self::getBackgroundCanvas($source, $color, $blurBackground, $blurValue, $boxWidth, $boxHeight);
227+
if ($canvas->compositeImage($clone, \Imagick::COMPOSITE_ATOP, $targetX, $targetY) !== true) {
228+
//cumbersome to test
229+
throw new \Exception('Imagick::compositeImage() did not return true');//@codeCoverageIgnore
230+
}
231+
232+
//reason we are not supporting the options in self::write() here is because format, and strip headers are
233+
//only relevant once written Imagick::stripImage() doesnt even have an effect until written
234+
//also the user can just call that function with the resultant $canvas
235+
$results[$boxSizeKey] = $canvas;
236+
}
237+
238+
foreach ($cloneCache as $clone) {
239+
$clone->destroy();
240+
}
241+
64242
return $results[0];
65243
}
66244

0 commit comments

Comments
 (0)