Magento 2 – Resize and Crop Images Without Stretching

imageimage resizingmagento2

I am trying to get magento 2 to resize my images to a set width and height then crop them to the correct dimensions. I am trying to do this via view.xml and php but both are running into the same problems

I have looked around at a few different guides and similar questions. http://devdocs.magento.com/guides/v2.0/frontend-dev-guide/themes/theme-images.html has instructions on what options I can change

Aspect Ratio: I do not want my images to be stretched so I set this to the true.

Frame: I do not want a frame around my image so I set this to false.

Constrain: This seems to have no effect on my images and only seems to be for cases where the image is smaller than the desired dimensions.

I have fooled around with different options and started looking into the core code. I can get an image the right size by turning aspect ratio off but the image becomes stretched. I can get an image the right size by turning on frames but that will add frames to the side/top/bottom of my image.

As far as I can tell Magento 2 has no way to remove parts of images with the default image resizer. It seems it can only resize and add frames. This is something that I know other systems can do (as I currently rebuilding a woocommerce site in magento 2) and will be something clients demand.

My current possible solutions to this are:

  • Crop all images outside magento then re-upload (this is unacceptable
    as client will continue to upload images with incorrect dimensions
    and we may want multiple aspect ratios within the system)

  • Hack/extend the core to add the ability to crop images when resizing.
    This does not look easy from what I have seen and I am curious if
    there is a 3rd party extension/easy solution that I have missed in my
    searching.

Best Answer

Managed to put together a working solution for now by overriding the magento 2 product image class

Create a new module with the standard module.xml, registration.php, composer.json as needed.

Crete etc/di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
  <preference for="Magento\Catalog\Model\Product\Image" type="Vendor\Imagecrop\Model\Product\Image" />
</config>

Create Model/Product/Image.php

<?php

namespace Vendor\Imagecrop\Model\Product;

class Image extends \Magento\Catalog\Model\Product\Image
{

  /**
   * @see \Magento\Framework\Image\Adapter\AbstractAdapter
   * @return $this
   */
  public function resize()
  {
    if (is_null($this->getWidth()) && is_null($this->getHeight())) {
      return $this;
    }
    // if given both dimensions crop to take up max of both dimensions
    if(!is_null($this->getWidth()) && !is_null($this->getHeight())){
      $new_width = $this->_width;
      $new_height = $this->_height;

      $this->getImageProcessor();

      $orig_width = $this->_processor->getOriginalWidth();
      $orig_height = $this->_processor->getOriginalHeight();

      $new_ratio = $new_width / $new_height;

      $orig_ratio = $orig_width / $orig_height;

      // if original has wider dimensions than new dimensions
      if($orig_ratio > $new_ratio){
        $temp_width = round($orig_ratio * $new_height);

        $tmp_constrain = $this->_constrainOnly;
        $this->setConstrainOnly(false);
        $this->getImageProcessor()->resize($temp_width, $new_height);
        $this->setConstrainOnly($tmp_constrain);

        $crop_amount = floor(($temp_width - $new_width) / 2);
        $crop_remainder = ($temp_width - $new_width) % 2;

        $this->_processor->crop(0, $crop_amount + $crop_remainder, $crop_amount, 0);


      // if original has taller dimensions than new dimensions
      } else {
        $temp_height = round((1 / $orig_ratio) * $new_width);

        $tmp_constrain = $this->_constrainOnly;
        $this->setConstrainOnly(false);
        $this->getImageProcessor()->resize($new_width, $temp_height);
        $this->setConstrainOnly($tmp_constrain);

        $crop_amount = floor(($temp_height - $new_height) / 2);
        $crop_remainder = ($temp_height - $new_height) % 2;

        $this->_processor->crop($crop_amount + $crop_remainder, 0, 0, $crop_amount);

      }

    } else {
      $this->getImageProcessor()->resize($this->_width, $this->_height);
    }
    return $this;
  }


}

This should change default behavious so when you have frame, aspect ratio set to false and constrain all set to true instead of resizing to the given size by stretching it will resize the shortest dimension and crop off the ends.

Still testing this code so use at own risk.