diff --git a/docs/3.0/api/crop.md b/docs/3.0/api/crop.md
index 46abc1f..537a36a 100644
--- a/docs/3.0/api/crop.md
+++ b/docs/3.0/api/crop.md
@@ -5,25 +5,25 @@ title: Crop
# Crop
-## Fit `fit=crop`
+## Fit `fit=cover` - Crop to cover the dimensions
Resizes the image to fill the width and height boundaries and crops any excess image data. The resulting image will match the width and height constraints without distorting the image.
~~~ html
-
+
~~~
[![© Photo Joel Reynolds](https://glide.herokuapp.com/1.0/kayaks.jpg?w=300&h=300&fit=crop)](https://glide.herokuapp.com/1.0/kayaks.jpg?w=300&h=300&fit=crop)
### Crop Position
-You can also set where the image is cropped by adding a crop position. Accepts `crop-top-left`, `crop-top`, `crop-top-right`, `crop-left`, `crop-center`, `crop-right`, `crop-bottom-left`, `crop-bottom` or `crop-bottom-right`. Default is `crop-center`, and is the same as `crop`.
+You can also set where the image is cropped by adding a crop position. Accepts `cover-top-left`, `cover-top`, `cover-top-right`, `cover-left`, `cover-center`, `cover-right`, `cover-bottom-left`, `cover-bottom` or `cover-bottom-right`. Default is `cover-center`, and is the same as `crop`.
~~~ html
-
+
~~~
-### Crop Focal Point
+## Fit `fit=crop-x%-y%` - Crop based on Focal Point
In addition to the crop position, you can be more specific about the exact crop position using a focal point. This is defined using two offset percentages: `crop-x%-y%`.
@@ -45,4 +45,4 @@ Crops the image to specific dimensions prior to any other resize operations. Req
~~~
-[![© Photo Joel Reynolds](https://glide.herokuapp.com/1.0/kayaks.jpg?crop=100,100,915,155)](https://glide.herokuapp.com/1.0/kayaks.jpg?crop=100,100,915,155)
\ No newline at end of file
+[![© Photo Joel Reynolds](https://glide.herokuapp.com/1.0/kayaks.jpg?crop=100,100,915,155)](https://glide.herokuapp.com/1.0/kayaks.jpg?crop=100,100,915,155)
diff --git a/docs/3.0/api/size.md b/docs/3.0/api/size.md
index 462a4fc..c40e7f3 100644
--- a/docs/3.0/api/size.md
+++ b/docs/3.0/api/size.md
@@ -29,14 +29,16 @@ Sets the height of the image, in pixels.
Sets how the image is fitted to its target dimensions.
-### Accepts:
+### Accepts:
- `contain`: Default. Resizes the image to fit within the width and height boundaries without cropping, distorting or altering the aspect ratio.
-- `max`: Resizes the image to fit within the width and height boundaries without cropping, distorting or altering the aspect ratio, and will also not increase the size of the image if it is smaller than the output size.
+- `max`: Resizes the image to fit within the width and height boundaries without cropping, distorting or altering the aspect ratio, and will also not increase the size of the image if it is smaller than the output size.
- `fill`: Resizes the image to fit within the width and height boundaries without cropping or distorting the image, and the remaining space is filled with the background color. The resulting image will match the constraining dimensions.
- `fill-max`: Resizes the image to fit within the width and height boundaries without cropping but upscaling the image if it's smaller. The finished image will have remaining space on either width or height (except if the aspect ratio of the new image is the same as the old image). The remaining space will be filled with the background color. The resulting image will match the constraining dimensions.
- `stretch`: Stretches the image to fit the constraining dimensions exactly. The resulting image will fill the dimensions, and will not maintain the aspect ratio of the input image.
-- `crop`: Resizes the image to fill the width and height boundaries and crops any excess image data. The resulting image will match the width and height constraints without distorting the image. See the [crop](api/crop/) page for more information.
+- `cover`: Resizes the image to fill the width and height boundaries and crops any excess image data. The resulting image will match the width and height constraints without distorting the image. See the [crop](api/crop/) page for more information.
+- `crop`: Resizes the image to fill the width and height boundaries and crops any excess image data. (alias for `cover`).
+- `crop-x%-y%`: Resizes the image to fill the width and height boundaries and crops based on a focal point defined by `x%` (left offset) and `y%` (top offset). The resulting image will match the width and height constraints without distorting the image. See the [crop](api/crop/) page for more information.
~~~ html
diff --git a/docs/3.0/api/watermarks.md b/docs/3.0/api/watermarks.md
index 71e9364..99c9b60 100644
--- a/docs/3.0/api/watermarks.md
+++ b/docs/3.0/api/watermarks.md
@@ -54,10 +54,11 @@ Sets how the watermark is fitted to its target dimensions.
- `max`: Resizes the image to fit within the width and height boundaries without cropping, distorting or altering the aspect ratio, and will also not increase the size of the image if it is smaller than the output size.
- `fill`: Resizes the image to fit within the width and height boundaries without cropping or distorting the image, and the remaining space is filled with the background color. The resulting image will match the constraining dimensions.
- `stretch`: Stretches the image to fit the constraining dimensions exactly. The resulting image will fill the dimensions, and will not maintain the aspect ratio of the input image.
-- `crop`: Resizes the image to fill the width and height boundaries and crops any excess image data. The resulting image will match the width and height constraints without distorting the image. See the [crop](api/crop/) page for more information.
+- `cover`: Resizes the image to fill the width and height boundaries and crops any excess image data. The resulting image will match the width and height constraints without distorting the image. See the [crop](api/crop/) page for more information.
+- `crop-x%-y%`: Resizes the image to fill the width and height boundaries and crops based on a focal point defined by `x%` (left offset) and `y%` (top offset). The resulting image will match the width and height constraints without distorting the image. See the [crop](api/crop/) page for more information.
~~~ html
-
+
~~~
## X-offset `markx`
diff --git a/docs/3.0/config/defaults-and-presets.md b/docs/3.0/config/defaults-and-presets.md
index 0826017..b889cbb 100644
--- a/docs/3.0/config/defaults-and-presets.md
+++ b/docs/3.0/config/defaults-and-presets.md
@@ -42,12 +42,12 @@ $server = League\Glide\ServerFactory::create([
'small' => [
'w' => 200,
'h' => 200,
- 'fit' => 'crop',
+ 'fit' => 'cover',
],
'medium' => [
'w' => 600,
'h' => 400,
- 'fit' => 'crop',
+ 'fit' => 'cover',
]
]
]);
@@ -57,12 +57,12 @@ $server->setPresets([
'small' => [
'w' => 200,
'h' => 200,
- 'fit' => 'crop',
+ 'fit' => 'cover',
],
'medium' => [
'w' => 600,
'h' => 400,
- 'fit' => 'crop',
+ 'fit' => 'cover',
]
]);
~~~
@@ -89,4 +89,4 @@ It's even possible to use presets with additional parameters:
## Overriding defaults and presets
-You can override the default and preset manipulations for a specific request by passing a new parameter (e.x. `mark=different.png`), or even disable it entirely by setting it to blank (e.x. `mark=`).
\ No newline at end of file
+You can override the default and preset manipulations for a specific request by passing a new parameter (e.x. `mark=different.png`), or even disable it entirely by setting it to blank (e.x. `mark=`).
diff --git a/docs/3.0/simple-example.md b/docs/3.0/simple-example.md
index 584ebc7..e15c302 100644
--- a/docs/3.0/simple-example.md
+++ b/docs/3.0/simple-example.md
@@ -16,7 +16,7 @@ In your templates simply define how the image will be manipulated. Using Glide's
=$user->name?>
-
+
~~~
For simplicity this example has omitted HTTP signatures, however in a production environment it's very important to secure your images.
diff --git a/src/Manipulators/Size.php b/src/Manipulators/Size.php
index ac4a12a..0d14bf9 100644
--- a/src/Manipulators/Size.php
+++ b/src/Manipulators/Size.php
@@ -107,11 +107,15 @@ public function getFit(): string
{
$fit = (string) $this->getParam('fit');
- if (in_array($fit, ['contain', 'fill', 'max', 'stretch', 'fill-max'], true)) {
+ if (in_array($fit, ['contain', 'fill', 'max', 'stretch', 'fill-max', 'cover'], true)) {
return $fit;
}
- if (preg_match('/^(crop)(-top-left|-top|-top-right|-left|-center|-right|-bottom-left|-bottom|-bottom-right|-[\d]{1,3}-[\d]{1,3}(?:-[\d]{1,3}(?:\.\d+)?)?)*$/', $fit)) {
+ if (preg_match('/^(crop|cover)(-top-left|-top|-top-right|-left|-center|-right|-bottom-left|-bottom|-bottom-right?)*$/', $fit)) {
+ return 'cover';
+ }
+
+ if (preg_match('/^(crop)(-[\d]{1,3}-[\d]{1,3}(?:-[\d]{1,3}(?:\.\d+)?)?)*$/', $fit)) {
return 'crop';
}
@@ -245,6 +249,10 @@ public function runResize(ImageInterface $image, string $fit, int $width, int $h
return $this->runStretchResize($image, $width, $height);
}
+ if ('cover' === $fit) {
+ return $this->runCoverResize($image, $width, $height);
+ }
+
if ('crop' === $fit) {
return $this->runCropResize($image, $width, $height);
}
@@ -344,6 +352,25 @@ public function runCropResize(ImageInterface $image, int $width, int $height): I
return $image->crop($width, $height, $offset_x, $offset_y);
}
+ /**
+ * Perform crop resize image manipulation.
+ *
+ * @param ImageInterface $image The source image.
+ * @param int $width The width.
+ * @param int $height The height.
+ * @param ?string $position The position of the crop
+ *
+ * @return ImageInterface The manipulated image.
+ */
+ public function runCoverResize(ImageInterface $image, int $width, int $height, ?string $position = null): ImageInterface
+ {
+ $position ??= str_replace(['crop-', 'cover-'], '', (string) $this->getParam('fit'));
+
+ $position = empty($position) || in_array($position, ['crop', 'cover']) ? 'center' : $position;
+
+ return $image->cover($width, $height, $position);
+ }
+
/**
* Resolve the crop resize dimensions.
*
diff --git a/tests/Manipulators/SizeTest.php b/tests/Manipulators/SizeTest.php
index 1153f9b..4c640c0 100644
--- a/tests/Manipulators/SizeTest.php
+++ b/tests/Manipulators/SizeTest.php
@@ -9,7 +9,7 @@
class SizeTest extends TestCase
{
- private $manipulator;
+ private Size $manipulator;
public function setUp(): void
{
@@ -21,23 +21,23 @@ public function tearDown(): void
\Mockery::close();
}
- public function testCreateInstance()
+ public function testCreateInstance(): void
{
- $this->assertInstanceOf('League\Glide\Manipulators\Size', $this->manipulator);
+ $this->assertInstanceOf(Size::class, $this->manipulator);
}
- public function testSetMaxImageSize()
+ public function testSetMaxImageSize(): void
{
$this->manipulator->setMaxImageSize(500 * 500);
$this->assertSame(500 * 500, $this->manipulator->getMaxImageSize());
}
- public function testGetMaxImageSize()
+ public function testGetMaxImageSize(): void
{
$this->assertNull($this->manipulator->getMaxImageSize());
}
- public function testRun()
+ public function testRun(): void
{
$image = \Mockery::mock(ImageInterface::class, function ($mock) {
$mock->shouldReceive('width')->andReturn('200')->twice();
@@ -51,7 +51,7 @@ public function testRun()
);
}
- public function testGetWidth()
+ public function testGetWidth(): void
{
$this->assertSame(100, $this->manipulator->setParams(['w' => 100])->getWidth());
$this->assertSame(100, $this->manipulator->setParams(['w' => 100.1])->getWidth());
@@ -60,7 +60,7 @@ public function testGetWidth()
$this->assertSame(null, $this->manipulator->setParams(['w' => '-100'])->getWidth());
}
- public function testGetHeight()
+ public function testGetHeight(): void
{
$this->assertSame(100, $this->manipulator->setParams(['h' => 100])->getHeight());
$this->assertSame(100, $this->manipulator->setParams(['h' => 100.1])->getHeight());
@@ -69,18 +69,25 @@ public function testGetHeight()
$this->assertSame(null, $this->manipulator->setParams(['h' => '-100'])->getHeight());
}
- public function testGetFit()
+ public function testGetFit(): void
{
$this->assertSame('contain', $this->manipulator->setParams(['fit' => 'contain'])->getFit());
$this->assertSame('fill', $this->manipulator->setParams(['fit' => 'fill'])->getFit());
$this->assertSame('fill-max', $this->manipulator->setParams(['fit' => 'fill-max'])->getFit());
$this->assertSame('max', $this->manipulator->setParams(['fit' => 'max'])->getFit());
$this->assertSame('stretch', $this->manipulator->setParams(['fit' => 'stretch'])->getFit());
- $this->assertSame('crop', $this->manipulator->setParams(['fit' => 'crop'])->getFit());
+ $this->assertSame('cover', $this->manipulator->setParams(['fit' => 'cover'])->getFit());
+ $this->assertSame('cover', $this->manipulator->setParams(['fit' => 'cover-top-left'])->getFit());
+ $this->assertSame('cover', $this->manipulator->setParams(['fit' => 'cover-center'])->getFit());
+ $this->assertSame('cover', $this->manipulator->setParams(['fit' => 'crop'])->getFit());
+ $this->assertSame('cover', $this->manipulator->setParams(['fit' => 'crop-bottom'])->getFit());
+ $this->assertSame('cover', $this->manipulator->setParams(['fit' => 'crop-top-left'])->getFit());
+ $this->assertSame('cover', $this->manipulator->setParams(['fit' => 'crop-center'])->getFit());
+ $this->assertSame('crop', $this->manipulator->setParams(['fit' => 'crop-25-75'])->getFit());
$this->assertSame('contain', $this->manipulator->setParams(['fit' => 'invalid'])->getFit());
}
- public function testGetCrop()
+ public function testGetCrop(): void
{
$this->assertSame([0, 0, 1.0], $this->manipulator->setParams(['fit' => 'crop-top-left'])->getCrop());
$this->assertSame([0, 100, 1.0], $this->manipulator->setParams(['fit' => 'crop-bottom-left'])->getCrop());
@@ -105,7 +112,7 @@ public function testGetCrop()
$this->assertSame([50, 50, 1.0], $this->manipulator->setParams(['fit' => 'invalid'])->getCrop());
}
- public function testGetDpr()
+ public function testGetDpr(): void
{
$this->assertSame(1.0, $this->manipulator->setParams(['dpr' => 'invalid'])->getDpr());
$this->assertSame(1.0, $this->manipulator->setParams(['dpr' => '-1'])->getDpr());
@@ -113,7 +120,7 @@ public function testGetDpr()
$this->assertSame(2.0, $this->manipulator->setParams(['dpr' => '2'])->getDpr());
}
- public function testResolveMissingDimensions()
+ public function testResolveMissingDimensions(): void
{
$image = \Mockery::mock(ImageInterface::class, function ($mock) {
$mock->shouldReceive('width')->andReturn(400);
@@ -125,7 +132,7 @@ public function testResolveMissingDimensions()
$this->assertSame([200, 100], $this->manipulator->resolveMissingDimensions($image, null, 100));
}
- public function testResolveMissingDimensionsWithOddDimensions()
+ public function testResolveMissingDimensionsWithOddDimensions(): void
{
$image = \Mockery::mock(ImageInterface::class, function ($mock) {
$mock->shouldReceive('width')->andReturn(1024);
@@ -135,7 +142,7 @@ public function testResolveMissingDimensionsWithOddDimensions()
$this->assertSame([411, 222], $this->manipulator->resolveMissingDimensions($image, 411, null));
}
- public function testLimitImageSize()
+ public function testLimitImageSize(): void
{
$this->assertSame([1000, 1000], $this->manipulator->limitImageSize(1000, 1000));
$this->manipulator->setMaxImageSize(500 * 500);
@@ -143,7 +150,7 @@ public function testLimitImageSize()
$this->assertSame([500, 500], $this->manipulator->limitImageSize(1000, 1000));
}
- public function testRunResize()
+ public function testRunResize(): void
{
$image = \Mockery::mock(ImageInterface::class, function ($mock) {
$mock->shouldReceive('width')->andReturn(100)->times(4);
@@ -186,13 +193,18 @@ public function testRunResize()
$this->manipulator->runResize($image, 'crop', 100, 100)
);
+ $this->assertInstanceOf(
+ ImageInterface::class,
+ $this->manipulator->runResize($image, 'crop-top-right', 100, 100)
+ );
+
$this->assertInstanceOf(
ImageInterface::class,
$this->manipulator->runResize($image, 'invalid', 100, 100)
);
}
- public function testRunContainResize()
+ public function testRunContainResize(): void
{
$image = \Mockery::mock(ImageInterface::class, function ($mock) {
$mock->shouldReceive('scale')->with(100, 100)->andReturn($mock)->once();
@@ -204,7 +216,7 @@ public function testRunContainResize()
);
}
- public function testRunFillResize()
+ public function testRunFillResize(): void
{
$image = \Mockery::mock(ImageInterface::class, function ($mock) {
$mock->shouldReceive('pad')->with(100, 100)->andReturn($mock)->once();
@@ -216,7 +228,7 @@ public function testRunFillResize()
);
}
- public function testRunMaxResize()
+ public function testRunMaxResize(): void
{
$image = \Mockery::mock(ImageInterface::class, function ($mock) {
$mock->shouldReceive('scaleDown')->with(100, 100)->andReturn($mock)->once();
@@ -228,7 +240,7 @@ public function testRunMaxResize()
);
}
- public function testRunStretchResize()
+ public function testRunStretchResize(): void
{
$image = \Mockery::mock(ImageInterface::class, function ($mock) {
$mock->shouldReceive('resize')->with(100, 100)->andReturn($mock)->once();
@@ -240,7 +252,7 @@ public function testRunStretchResize()
);
}
- public function testRunCropResize()
+ public function testRunCropResize(): void
{
$image = \Mockery::mock(ImageInterface::class, function ($mock) {
$mock->shouldReceive('width')->andReturn(100)->times(4);
@@ -251,11 +263,58 @@ public function testRunCropResize()
$this->assertInstanceOf(
ImageInterface::class,
- $this->manipulator->runCropResize($image, 100, 100, 'center')
+ $this->manipulator->runCropResize($image, 100, 100)
+ );
+ }
+
+ public function testRunCoverResize(): void
+ {
+ $image = \Mockery::mock(ImageInterface::class, function ($mock) {
+ $mock->shouldReceive('width')->andReturn(100);
+ $mock->shouldReceive('height')->andReturn(100);
+ $mock->shouldReceive('cover')->with(50, 50, 'center')->andReturn($mock)->twice();
+ });
+
+ $this->assertInstanceOf(
+ ImageInterface::class,
+ $this->manipulator->setParams(['w' => 50, 'h' => 50, 'fit' => 'cover'])->run($image)
+ );
+
+ $this->assertInstanceOf(
+ ImageInterface::class,
+ $this->manipulator->setParams(['w' => 50, 'h' => 50, 'fit' => 'crop'])->run($image)
+ );
+ }
+
+ public function testRunCoverResizePosition(): void
+ {
+ $image = \Mockery::mock(ImageInterface::class, function ($mock) {
+ $mock->shouldReceive('width')->andReturn(100);
+ $mock->shouldReceive('height')->andReturn(100);
+ $mock->shouldReceive('cover')->with(50, 50, 'top-left')->andReturn($mock)->twice();
+ $mock->shouldReceive('cover')->with(50, 50, 'bottom')->andReturn($mock)->once();
+ $mock->shouldReceive('cover')->with(50, 50, 'bottom-right')->andReturn($mock)->once();
+ });
+
+ $this->assertInstanceOf(
+ ImageInterface::class,
+ $this->manipulator->setParams(['w' => 50, 'h' => 50, 'fit' => 'crop-top-left'])->run($image)
+ );
+ $this->assertInstanceOf(
+ ImageInterface::class,
+ $this->manipulator->setParams(['w' => 50, 'h' => 50, 'fit' => 'cover-top-left'])->run($image)
+ );
+ $this->assertInstanceOf(
+ ImageInterface::class,
+ $this->manipulator->setParams(['w' => 50, 'h' => 50, 'fit' => 'crop-bottom'])->run($image)
+ );
+ $this->assertInstanceOf(
+ ImageInterface::class,
+ $this->manipulator->setParams(['w' => 50, 'h' => 50, 'fit' => 'crop-bottom-right'])->run($image)
);
}
- public function testResizeDoesNotRunWhenNoParamsAreSet()
+ public function testResizeDoesNotRunWhenNoParamsAreSet(): void
{
$image = \Mockery::mock(ImageInterface::class, function ($mock) {
$mock->shouldReceive('width')->andReturn(100)->twice();
@@ -269,7 +328,7 @@ public function testResizeDoesNotRunWhenNoParamsAreSet()
);
}
- public function testResizeDoesNotRunWhenSettingFitCropToCenterWithNoZoom()
+ public function testResizeDoesNotRunWhenSettingFitCropToCenterWithNoZoom(): void
{
$image = \Mockery::mock(ImageInterface::class, function ($mock) {
$mock->shouldReceive('width')->andReturn(100)->twice();
@@ -285,7 +344,7 @@ public function testResizeDoesNotRunWhenSettingFitCropToCenterWithNoZoom()
);
}
- public function testResizeDoesRunWhenDimensionsAreTheSameAndTheCropZoomIsNotDefaultOne()
+ public function testResizeDoesRunWhenDimensionsAreTheSameAndTheCropZoomIsNotDefaultOne(): void
{
$image = \Mockery::mock(ImageInterface::class, function ($mock) {
$mock->shouldReceive('width')->andReturn(100);