diff --git a/README.md b/README.md
index ac4e966..abd9f43 100644
--- a/README.md
+++ b/README.md
@@ -16,8 +16,9 @@ Currently, Focal Point integrates with the standard image fields.
 ##DEPENDENCIES
 
 - image
+- crop
 
-##USUAGE
+##USAGE
 
 ### Setting up image fields
 
diff --git a/config/install/crop.type.focal_point.yml b/config/install/crop.type.focal_point.yml
new file mode 100644
index 0000000..a78d87d
--- /dev/null
+++ b/config/install/crop.type.focal_point.yml
@@ -0,0 +1,7 @@
+langcode: en
+status: true
+dependencies: {  }
+label: 'Focal point'
+id: 'focal_point'
+description: 'Crop type used by Focal point module.'
+aspect_ratio: ''
diff --git a/config/install/focal_point.settings.yml b/config/install/focal_point.settings.yml
new file mode 100644
index 0000000..947a6ce
--- /dev/null
+++ b/config/install/focal_point.settings.yml
@@ -0,0 +1,3 @@
+crop_type: 'focal_point'
+default_value: '50,50'
+
diff --git a/config/schema/focal_point.schema.yml b/config/schema/focal_point.schema.yml
new file mode 100644
index 0000000..bee70d9
--- /dev/null
+++ b/config/schema/focal_point.schema.yml
@@ -0,0 +1,10 @@
+focal_point.settings:
+  type: config_object
+  label: 'Focal point settings'
+  mapping:
+    crop_type:
+      type: string
+      label: 'Crop type to be used with Focal point'
+    default_value:
+      type: string
+      label: 'The default value to use for focal point when none is specified'
diff --git a/focal_point.info.yml b/focal_point.info.yml
index 59a2d6f..f2d7bdf 100644
--- a/focal_point.info.yml
+++ b/focal_point.info.yml
@@ -6,3 +6,4 @@ package: Images
 version: VERSION
 dependencies:
   - image
+  - crop
diff --git a/focal_point.install b/focal_point.install
index 736cd6f..6551772 100644
--- a/focal_point.install
+++ b/focal_point.install
@@ -5,37 +5,82 @@
  * Install hooks for focal_point.
  */
 
+/**
+ * Implements hook_requirements().
+ */
+function focal_point_requirements($phase) {
+  if ($phase == 'update' && !\Drupal::moduleHandler()->moduleExists('crop')) {
+    return [
+      'crop' => [
+        'title' => t('Focal point'),
+        'value' => t('Crop API missing'),
+        'description' => t(
+        '<a href=":url">Crop API</a> module is now a dependency and needs to be installed before running updates.',
+          [':url' => 'https://www.drupal.org/project/crop']
+        ),
+        'severity' => REQUIREMENT_ERROR,
+      ],
+    ];
+  }
+}
 
 /**
- * Implements hook_uninstall().
+ * Install default config.
  */
-function focal_point_uninstall() {
+function focal_point_update_8001() {
+  if (!\Drupal::moduleHandler()->moduleExists('crop')) {
+    throw new \Drupal\Core\Utility\UpdateException('Crop API (drupal.org/project/crop) module is now a dependency and needs to be installed before running updates.');
+  }
 
+  \Drupal::service('config.installer')
+    ->installDefaultConfig('module', 'focal_point');
 }
 
 /**
- * Implements hook_schema().
+ * Migrates legacy values to crop entities.
  */
-function focal_point_schema() {
-  // @todo: is there a way to alter the schema of the field table(s) instead?
-  // @todo: should I be using the UUID from file_managed table here instead?
-  $schema['focal_point'] = array(
-    'fields' => array(
-      'fid' => array(
-        'description' => 'File ID.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'focal_point' => array(
-        'type' => 'varchar',
-        'length' => 7, // The longest possible value of this is 100,100
-        'not null' => TRUE,
-        'default' => '',
-      ),
-    ),
-    'primary key' => array('fid'),
-  );
-
-  return $schema;
+function focal_point_update_8002(&$sandbox) {
+  $file_storage = \Drupal::entityTypeManager()->getStorage('file');
+  $crop_storage = \Drupal::entityTypeManager()->getStorage('crop');
+  $crop_type = \Drupal::config('focal_point.settings')->get('crop_type');
+  if (!isset($sandbox['num_processed'])) {
+    $sandbox['last_fid'] = 0;
+    $sandbox['num_processed'] = 0;
+    $sandbox['total_items'] = \Drupal::database()
+      ->select('focal_point', 'fp')
+      ->countQuery()
+      ->execute()
+      ->fetchField();
+  }
+
+  $focal_points = \Drupal::database()
+    ->select('focal_point', 'fp')
+    ->fields('fp')
+    ->condition('fp.fid', $sandbox['last_fid'], '>')
+    ->range(0, 100)
+    ->orderBy('fp.fid')
+    ->execute();
+
+  foreach ($focal_points as $focal_point) {
+    /** @var \Drupal\file\FileInterface $file */
+    $file = $file_storage->load($focal_point->fid);
+    $size = getimagesize($file->getFileUri());
+    $focal_point = explode(',', $focal_point->focal_point);
+    $crop_storage
+      ->create([
+        'type' => $crop_type,
+        'entity_id' => $file->id(),
+        'entity_type' => 'file',
+        'uri' => $file->getFileUri(),
+        'x' => (int) round((intval($focal_point[0]) / 100.) * $size[0]),
+        'y' => (int) round((intval($focal_point[1]) / 100.) * $size[1]),
+      ])
+      ->save();
+    $sandbox['num_processed']++;
+    $sandbox['last_fid'] = $file->id();
+  }
+
+  $sandbox['#finished'] = $sandbox['num_processed'] / (float) $sandbox['total_items'];
+
+  // Intentionally leaving legacy table. You never know...
 }
diff --git a/focal_point.module b/focal_point.module
index 221b06f..44903b1 100644
--- a/focal_point.module
+++ b/focal_point.module
@@ -11,7 +11,7 @@
  */
 
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\focal_point\FocalPoint;
+use Drupal\crop\Entity\Crop;
 
 /**
  * Implements hook_entity_presave().
@@ -23,12 +23,42 @@ function focal_point_entity_presave(EntityInterface $entity) {
   if ($entity instanceof Drupal\Core\Entity\FieldableEntityInterface) {
     // Loop all the fields and save focal point values for images.
     foreach ($entity->getFieldDefinitions() as $key => $value) {
-      if ($value->getType() == 'image' && $entity->{$value->getName()} !== NULL) {
+      if ($value->getType() == 'image' && $entity->hasField($value->getName())) {
         // Loop through all values for this field. Its cardinality might be > 1.
         foreach ($entity->{$value->getName()} as $item) {
           if (isset($item->focal_point)) {
-            $focal_point = new FocalPoint($item->target_id);
-            $focal_point->saveFocalPoint($item->focal_point);
+            list($x, $y) = explode(',', $item->focal_point);
+
+            // Focal point JS provides relative location while crop entity
+            // expects exact coordinate on the original image. Let's convert.
+            $x = (int) round(($x / 100.0) * $item->width);
+            $y = (int) round(($y / 100.0) * $item->height);
+
+            $crop_type = \Drupal::config('focal_point.settings')->get('crop_type');
+            if (Crop::cropExists($item->entity->getFileUri(), $crop_type)) {
+              /** @var \Drupal\crop\CropInterface $crop */
+              $crop = Crop::findCrop($item->entity->getFileUri(), $crop_type);
+              if ($crop->x->value != $x || $crop->y->value != $y) {
+                $crop->x = $x;
+                $crop->y = $y;
+                $crop->save();
+              }
+            }
+            else {
+              $values = [
+                'type' => $crop_type,
+                'entity_id' => $item->target_id,
+                'entity_type' => 'file',
+                'uri' => $item->entity->getFileUri(),
+                'x' => $x,
+                'y' => $y,
+              ];
+
+              \Drupal::entityTypeManager()
+                ->getStorage('crop')
+                ->create($values)
+                ->save();
+            }
           }
         }
       }
diff --git a/src/FocalPoint.php b/src/FocalPoint.php
deleted file mode 100644
index 5d2fbb7..0000000
--- a/src/FocalPoint.php
+++ /dev/null
@@ -1,211 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\focal_point\FocalPoint.
- */
-
-namespace Drupal\focal_point;
-
-use Drupal\Core\Cache\Cache;
-use Drupal\file\Entity\File;
-
-/**
- * Defines the FocalPoint class.
- */
-class FocalPoint {
-
-  /**
-   * The default value to use for focal point when non is specified.
-   */
-  const DEFAULT_VALUE = '50,50';
-
-  /**
-   * The file entity id to which this focal point object applies.
-   *
-   * @var int
-   */
-  private $fid;
-
-  /**
-   * The focal point coordinates.
-   *
-   * @var string
-   *   A string in the form ##,##.
-   */
-  private $focalPoint;
-
-  /**
-   * Constructs a Focal Point object.
-   *
-   * @param int $fid
-   */
-  public function __construct($fid) {
-    $this->fid = $fid;
-    $this->focalPoint = $this->getFocalPoint();
-  }
-
-  /**
-   * Implements \Drupal\focal_point\FocalPoint::getFocalPoint().
-   *
-   * Get the focal point value for a given file entity. If none is found, return
-   * an empty string.
-   *
-   * @return string
-   */
-  public function getFocalPoint() {
-    if (is_null($this->focalPoint)) {
-      $result = self::getFocalPoints(array($this->fid));
-      $this->focalPoint = isset($result[$this->fid]) ? $result[$this->fid] : '';
-    }
-
-    return $this->focalPoint;
-  }
-
-  /**
-   * Implements \Drupal\focal_point\FocalPoint::getFocalPoints().
-   *
-   * Get the focal point values in an array keyed by fid for the given file
-   * entities. If none is found for any of the given files, the value for that
-   * file will be an empty string.
-   *
-   * @param array $fids
-   *
-   * @return array
-   */
-  public static function getFocalPoints(array $fids) {
-    $focal_points =  &drupal_static(__METHOD__, array());
-
-    $missing = array_diff($fids, array_keys($focal_points));
-    if ($missing) {
-      $result = db_query('SELECT fid, focal_point FROM {focal_point} WHERE fid IN (:fids[])', array(':fids[]' => $missing))->fetchAllKeyed();
-      $focal_points += $result;
-    }
-
-    return array_intersect_key($focal_points, array_combine($fids, $fids));
-  }
-
-  /**
-   * Implements \Drupal\focal_point\FocalPoint::getFromURI().
-   *
-   * Get the focal point value for a given file based on its URI. If none is
-   * found, return an empty string.
-   *
-   * @param string $uri
-   *
-   * @return string
-   *
-   * @todo Figure out a better way of doing this. Right now its needed by the
-   *   focal point image effect but it seems wrong.
-   */
-  public static function getFromURI($uri) {
-    $query = db_select('focal_point', 'fp')
-      ->fields('fp', array('focal_point'));
-    $query->join('file_managed', 'fm', 'fp.fid = fm.fid');
-    $query->condition('fm.uri', $uri);
-    $focal_point = $query->execute()->fetchField();
-
-    return $focal_point;
-  }
-
-  /**
-   * Implements \Drupal\focal_point\FocalPoint::fid().
-   *
-   * Returns the file entity id to which this focal point object applies.
-   *
-   * @return int|null
-   */
-  public function fid() {
-    return isset($this->fid) ? $this->fid : NULL;
-  }
-
-  /**
-   * Implements \Drupal\focal_point\FocalPoint::setFocalPoint().
-   *
-   * Save the given focal point value for the given file to the database.
-   *
-   * @param string $focal_point
-   */
-  public function saveFocalPoint($focal_point) {
-    // If the focal point has not changed, then there is nothing to see here.
-    if ($this->focalPoint !== $focal_point) {
-      \Drupal::database()->merge('focal_point')
-        ->key(array('fid' => $this->fid))
-        ->fields(array('focal_point' => $focal_point))
-        ->execute();
-
-      $this->flush($this->fid);
-
-      // Clear caches and static variables.
-      $focal_points =  &drupal_static('getFocalPoints', array());
-      unset($focal_points[$this->fid]);
-      Cache::invalidateTags(array('file:' . $this->fid));
-    }
-  }
-
-  /**
-   * Implements \Drupal\focal_point\FocalPoint::delete().
-   *
-   * Deletes the focal point values for the given file from the database.
-   *
-   * @param int $fid
-   */
-  public function delete($fid) {
-    $this->flush($fid);
-
-    db_delete('focal_point')
-      ->condition('fid', $fid)
-      ->execute();
-  }
-
-  /**
-   * Implements \Drupal\focal_point\FocalPoint::flush().
-   *
-   * Flush all image derivatives for the given file.
-   *
-   * @param int $fid
-   */
-  public function flush($fid) {
-    $file = File::load($fid);
-    image_path_flush($file->getFileUri());
-  }
-
-  /**
-   * Implements \Drupal\focal_point\FocalPoint::parse().
-   *
-   * Return the given focal point value broken out into its component pieces as
-   * an array in the following form:
-   *   - x-offset: x value
-   *   - y-offset: y value
-   * If all else fails, return the parsed default focal point value.
-   *
-   * @param string $focal_point
-   *
-   * @return array
-   */
-  public static function parse($focal_point) {
-    if (empty($focal_point) || !self::validate($focal_point)) {
-      $focal_point = self::DEFAULT_VALUE;
-    }
-
-    return array_combine(array('x-offset', 'y-offset'), explode(',', $focal_point));
-  }
-
-  /**
-   * Implements \Drupal\focal_point\FocalPoint::validate().
-   *
-   * Decides if the given focal point value is valid.
-   *
-   * @param string $focal_point
-   *
-   * @return bool
-   */
-  public static function validate($focal_point) {
-    if (preg_match('/^(100|[0-9]{1,2})(,)(100|[0-9]{1,2})$/', $focal_point)) {
-      return TRUE;
-    }
-    else {
-      return FALSE;
-    }
-  }
-}
diff --git a/src/FocalPointEffectBase.php b/src/FocalPointEffectBase.php
index b1cdae2..940b1b1 100644
--- a/src/FocalPointEffectBase.php
+++ b/src/FocalPointEffectBase.php
@@ -7,12 +7,70 @@
 
 namespace Drupal\focal_point;
 
+use Drupal\Core\Config\ImmutableConfig;
+use Drupal\Core\Image\ImageInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\crop\CropInterface;
+use Drupal\crop\CropStorageInterface;
 use Drupal\image\Plugin\ImageEffect\ResizeImageEffect;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Provides a base class for image effects.
  */
-abstract class FocalPointEffectBase extends ResizeImageEffect {
+abstract class FocalPointEffectBase extends ResizeImageEffect implements ContainerFactoryPluginInterface {
+
+  /**
+   * Crop storage.
+   *
+   * @var \Drupal\crop\CropStorageInterface
+   */
+  protected $cropStorage;
+
+  /**
+   * Focal point configuration object.
+   *
+   * @var \Drupal\Core\Config\ImmutableConfig
+   */
+  protected $focalPointConfig;
+
+  /**
+   * Constructs a \Drupal\focal_point\FocalPointEffectBase object.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Psr\Log\LoggerInterface $logger
+   *   Image logger.
+   * @param \Drupal\crop\CropStorageInterface $crop_storage
+   *   Crop storage.
+   * @param \Drupal\Core\Config\ImmutableConfig $config
+   *   Focal point configuration object.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, LoggerInterface $logger, CropStorageInterface $crop_storage, ImmutableConfig $config) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $logger);
+    $this->cropStorage = $crop_storage;
+    $this->focalPointConfig = $config;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('logger.factory')->get('image'),
+      $container->get('entity_type.manager')->getStorage('crop'),
+      $container->get('config.factory')->get('focal_point.settings')
+    );
+  }
+
   /**
    * Calculate the resize dimensions of an image based on the longest crop
    * dimension so that the aspect ratio is preserved and that there is always
@@ -53,64 +111,44 @@ abstract class FocalPointEffectBase extends ResizeImageEffect {
   }
 
   /**
-   * Compile the necessary data for the image crop effect.
+   * Calculate the crop anchor.
    *
-   * @param string $focal_point
-   * @param int $image_width
-   * @param int $image_height
-   * @param int $crop_width
-   * @param int $crop_height
+   * This is based on Crop's anchor function with the additional logic which makes
+   * sure that crop area doesn't fall out of the original image.
    *
-   * @return array|bool
-   *   An array containing the following keys:
-   *    - width
-   *    - height
-   *    - x
-   *    - y
-   */
-  public static function calculateCropData($focal_point, $image_width, $image_height, $crop_width, $crop_height) {
-    $crop_data = array();
-    $parsed_focal_point = FocalPoint::parse($focal_point);
-
-    // Get the pixel location of the focal point for the current image taking
-    // the image boundaries into account.
-    $crop_data['width'] = (int) $crop_width;
-    $crop_data['height'] = (int) $crop_height;
-    $crop_data['x'] = self::calculateAnchor($image_width, $crop_width, $parsed_focal_point['x-offset']);
-    $crop_data['y'] = self::calculateAnchor($image_height, $crop_height, $parsed_focal_point['y-offset']);
-
-    return $crop_data;
-  }
-
-  /**
-   * Calculate the anchor offset for the given dimension.
-   *
-   * @param int $image_size
-   *   The dimension of the full-sized image.
-   * @param int $crop_size
-   *   The dimension of the crop.
-   * @param int $focal_point_offset
-   *   The corresponding focal point percentage value for the given dimension.
+   * @param \Drupal\Core\Image\ImageInterface $image
+   *   Image object representing original image.
+   * @param \Drupal\crop\CropInterface
+   *   Crop entity.
    *
-   * @return int
+   * @return array
+   *   Array with two keys (x, y) and anchor coordinates as values.
    */
-  public static function calculateAnchor($image_size, $crop_size, $focal_point_offset) {
-    $focal_point_pixel = (int) $focal_point_offset * $image_size / 100;
-
-    // If the crop size is larger than the image size, use the image size to avoid
-    // stretching. This will cause the excess space to be filled with black.
-    $crop_size = min($image_size, $crop_size);
-
-    // Define the anchor as half the crop width to the left.
-    $offset = (int) ($focal_point_pixel - (.5 * $crop_size));
-    // Ensure the anchor doesn't fall off the left edge of the image.
-    $offset = max($offset, 0);
-    // Ensure the anchor doesn't fall off the right side of the image.
-    if ($offset + $crop_size > $image_size) {
-      $offset = $image_size - $crop_size;
-    }
-
-    return $offset;
+  protected function calculateAnchor(ImageInterface $image, CropInterface $crop) {
+    // Ensure the anchor doesn't fall off the left/top edge of the image.
+    $anchor = array_map(
+      function ($value) { return max($value, 0); },
+      $crop->anchor()
+    );
+
+    // Make sure X is always first item.
+    ksort($anchor);
+
+    // Ensure the anchor doesn't fall off the right/bottom edge of the image.
+    $anchor = array_map(
+      function ($value, $crop_size, $image_size) {
+        // If the crop size is larger than the image size, use the image size to
+        // avoid stretching. This will cause the excess space to be filled with
+        // black.
+        $crop_size = min($image_size, $crop_size);
+        return ($value + $crop_size > $image_size) ? $image_size - $crop_size : $value;
+      },
+      $anchor,
+      [$crop->width->value, $crop->height->vaue],
+      [$image->getWidth(), $image->getHeight()]
+    );
+
+    return $anchor;
   }
 
 }
diff --git a/src/Plugin/Field/FieldWidget/FocalPointImageWidget.php b/src/Plugin/Field/FieldWidget/FocalPointImageWidget.php
index 4d43fa4..8d08b71 100644
--- a/src/Plugin/Field/FieldWidget/FocalPointImageWidget.php
+++ b/src/Plugin/Field/FieldWidget/FocalPointImageWidget.php
@@ -7,7 +7,10 @@
 
 namespace Drupal\focal_point\Plugin\Field\FieldWidget;
 
-use Drupal\focal_point\FocalPoint;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Render\ElementInfoManagerInterface;
+use Drupal\Core\StringTranslation\ranslationTrait;
+use Drupal\crop\Entity\Crop;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\image\Plugin\Field\FieldWidget\ImageWidget;
 
@@ -23,6 +26,13 @@ use Drupal\image\Plugin\Field\FieldWidget\ImageWidget;
 class FocalPointImageWidget extends ImageWidget {
 
   /**
+   * Regular expression for focal point form value validation.
+   *
+   * @var string
+   */
+  const VALIDATION_REGEXP = '/^(100|[0-9]{1,2})(,)(100|[0-9]{1,2})$/';
+
+  /**
    * {@inheritDocs}
    *
    * Form API callback: Processes a image_fp field element.
@@ -65,7 +75,7 @@ class FocalPointImageWidget extends ImageWidget {
       '#type' => 'textfield',
       '#title' => 'Focal point',
       '#description' => t('Specify the focus of this image in the form "leftoffset,topoffset" where offsets are in percents. Ex: 25,75'),
-      '#default_value' => isset($item['focal_point']) ? $item['focal_point'] : FocalPoint::DEFAULT_VALUE,
+      '#default_value' => isset($item['focal_point']) ? $item['focal_point'] : \Drupal::config('focal_point.settings')->get('default_value'),
       '#element_validate' => array('\Drupal\focal_point\Plugin\Field\FieldWidget\FocalPointImageWidget::validateFocalPoint'),
       '#attributes' => array(
         'class' => array('focal-point', 'focal-point-' . $element['#field_name'] . '-' . $element['#delta']),
@@ -93,8 +103,14 @@ class FocalPointImageWidget extends ImageWidget {
     // When an element is loaded, focal_point needs to be set. During a form
     // submission the value will already be there.
     if (isset($return['target_id']) && !isset($return['focal_point'])) {
-      $element['#focal_point'] = new FocalPoint($return['target_id']);
-      $return['focal_point'] = $element['#focal_point']->getFocalPoint();
+      /** @var \Drupal\file\FileInterface $file */
+      $file = \Drupal::service('entity_type.manager')->getStorage('file')->load($return['target_id']);
+      $crop = Crop::findCrop($file->getFileUri(), \Drupal::config('focal_point.settings')->get('crop_type'));
+      if ($crop) {
+        $x = (int) round($crop->x->value / $return['width'] * 100);
+        $y = (int) round($crop->y->value / $return['height'] * 100);
+        $return['focal_point'] = "$x,$y";
+      }
     }
     return $return;
   }
@@ -108,8 +124,8 @@ class FocalPointImageWidget extends ImageWidget {
     $field_name = array_pop($element['#parents']);
     $focal_point_value = $form_state->getValue($field_name);
 
-    if (!is_null($focal_point_value) && !FocalPoint::validate($focal_point_value)) {
-      \Drupal::formBuilder()->setError($element, $form_state, t('The !title field should be in the form "leftoffset,topoffset" where offsets are in percents. Ex: 25,75.', array('!title' => $element['#title'])));
+    if (!is_null($focal_point_value) && !preg_match(static::VALIDATION_REGEXP, $focal_point_value)) {
+      $form_state->setError($element, \Drupal::translation()->translate('The !title field should be in the form "leftoffset,topoffset" where offsets are in percents. Ex: 25,75.', array('!title' => $element['#title'])));
     }
   }
 
diff --git a/src/Plugin/ImageEffect/FocalPointCropImageEffect.php b/src/Plugin/ImageEffect/FocalPointCropImageEffect.php
index e4211c8..b8330a9 100644
--- a/src/Plugin/ImageEffect/FocalPointCropImageEffect.php
+++ b/src/Plugin/ImageEffect/FocalPointCropImageEffect.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\focal_point\Plugin\ImageEffect;
 
-use Drupal\focal_point\FocalPoint;
+use Drupal\crop\Entity\Crop;
 use Drupal\focal_point\FocalPointEffectBase;
 use Drupal\Core\Image\ImageInterface;
 
@@ -27,11 +27,34 @@ class FocalPointCropImageEffect extends FocalPointEffectBase {
    * {@inheritdoc}
    */
   public function applyEffect(ImageInterface $image) {
-    // Next, attempt to crop the image.
-    $focal_point = FocalPoint::getFromURI($image->getSource());
-    $crop_data = self::calculateCropData($focal_point, $image->getWidth(), $image->getHeight(), $this->configuration['width'], $this->configuration['height']);
-    if (!$image->crop($crop_data['x'], $crop_data['y'], $crop_data['width'], $crop_data['height'])) {
-      $this->logger->error('Focal point scale and crop failed while scaling and cropping using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->getMimeType(), '%dimensions' => $image->getWidth() . 'x' . $image->getHeight()));
+    $crop_type = $this->focalPointConfig->get('crop_type');
+    /** @var \Drupal\crop\CropInterface $crop */
+    if ($crop = Crop::findCrop($image->getSource(), $crop_type)) {
+      $crop->width = $this->configuration['width'];
+      $crop->height = $this->configuration['height'];
+    }
+    else {
+      $crop = $this->cropStorage->create([
+        'type' => $crop_type,
+        'x' => (int) round($image->getWidth() / 2),
+        'y' => (int) round($image->getHeight() /2),
+        'width' => $this->configuration['width'],
+        'height' => $this->configuration['height'],
+      ]);
+    }
+
+    $anchor = $this->calculateAnchor($image, $crop);
+    if (!$image->crop($anchor['x'], $anchor['y'], $this->configuration['width'], $this->configuration['height'])) {
+      $this->logger->error(
+        'Focal point scale and crop failed while scaling and cropping using the %toolkit toolkit on %path (%mimetype, %dimensions, anchor: %anchor)',
+        [
+          '%toolkit' => $image->getToolkitId(),
+          '%path' => $image->getSource(),
+          '%mimetype' => $image->getMimeType(),
+          '%dimensions' => $image->getWidth() . 'x' . $image->getHeight(),
+          '%anchor' => $anchor,
+        ]
+      );
       return FALSE;
     }
 
diff --git a/src/Plugin/ImageEffect/FocalPointScaleAndCropImageEffect.php b/src/Plugin/ImageEffect/FocalPointScaleAndCropImageEffect.php
index 97fbba5..2ec5c8f 100644
--- a/src/Plugin/ImageEffect/FocalPointScaleAndCropImageEffect.php
+++ b/src/Plugin/ImageEffect/FocalPointScaleAndCropImageEffect.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\focal_point\Plugin\ImageEffect;
 
-use Drupal\focal_point\FocalPoint;
+use Drupal\crop\Entity\Crop;
 use Drupal\focal_point\FocalPointEffectBase;
 use Drupal\Core\Image\ImageInterface;
 
@@ -30,15 +30,47 @@ class FocalPointScaleAndCropImageEffect extends FocalPointEffectBase {
     // First, attempt to resize the image.
     $resize_data = self::calculateResizeData($image->getWidth(), $image->getHeight(), $this->configuration['width'], $this->configuration['height']);
     if (!$image->resize($resize_data['width'], $resize_data['height'])) {
-      watchdog('image', 'Focal point scale and crop failed while resizing using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->getMimeType(), '%dimensions' => $image->getWidth() . 'x' . $image->getHeight()), WATCHDOG_ERROR);
+      $this->logger->error(
+        'Focal point scale and crop failed while resizing using the %toolkit toolkit on %path (%mimetype, %dimensions)',
+        [
+          '%toolkit' => $image->getToolkitId(),
+          '%path' => $image->getSource(),
+          '%mimetype' => $image->getMimeType(),
+          '%dimensions' => $image->getWidth() . 'x' . $image->getHeight(),
+        ]
+      );
       return FALSE;
     }
 
     // Next, attempt to crop the image.
-    $focal_point = FocalPoint::getFromURI($image->getSource());
-    $crop_data = self::calculateCropData($focal_point, $image->getWidth(), $image->getHeight(), $this->configuration['width'], $this->configuration['height']);
-    if (!$image->crop($crop_data['x'], $crop_data['y'], $crop_data['width'], $crop_data['height'])) {
-      watchdog('image', 'Focal point scale and crop failed while scaling and cropping using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->getMimeType(), '%dimensions' => $image->getWidth() . 'x' . $image->getHeight()), WATCHDOG_ERROR);
+    $crop_type = $this->focalPointConfig->get('crop_type');
+    /** @var \Drupal\crop\CropInterface $crop */
+    if ($crop = Crop::findCrop($image->getSource(), $crop_type)) {
+      $crop->width = $this->configuration['width'];
+      $crop->height = $this->configuration['height'];
+    }
+    else {
+      $crop = $this->cropStorage->create([
+        'type' => $crop_type,
+        'x' => (int) round($image->getWidth() / 2),
+        'y' => (int) round($image->getHeight() /2),
+        'width' => $this->configuration['width'],
+        'height' => $this->configuration['height'],
+      ]);
+    }
+
+    $anchor = $this->calculateAnchor($image, $crop);
+    if (!$image->crop($anchor['x'], $anchor['y'], $this->configuration['width'], $this->configuration['height'])) {
+      $this->logger->error(
+        'Focal point scale and crop failed while scaling and cropping using the %toolkit toolkit on %path (%mimetype, %dimensions, anchor: %anchor).',
+        [
+          '%toolkit' => $image->getToolkitId(),
+          '%path' => $image->getSource(),
+          '%mimetype' => $image->getMimeType(),
+          '%dimensions' => $image->getWidth() . 'x' . $image->getHeight(),
+          '%anchor' => $anchor,
+        ]
+      );
       return FALSE;
     }
 
diff --git a/tests/src/Unit/Effects/FocalPointEffectsTest.php b/tests/src/Unit/Effects/FocalPointEffectsTest.php
index 8f8f0f6..374ab2e 100644
--- a/tests/src/Unit/Effects/FocalPointEffectsTest.php
+++ b/tests/src/Unit/Effects/FocalPointEffectsTest.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\Tests\focal_point\Unit\Effects;
 
-use Drupal\Tests\UnitTestCase;
+use Drupal\crop\Entity\Crop;
+use Drupal\focal_point\Plugin\ImageEffect\FocalPointCropImageEffect;
+use Drupal\KernelTests\KernelTestBase;
 use Drupal\focal_point\FocalPointEffectBase;
 
 /**
@@ -18,7 +20,18 @@ use Drupal\focal_point\FocalPointEffectBase;
  *
  * @see \Drupal\focal_point\FocalPointEffectBase
  */
-class FocalPointEffectsTest extends UnitTestCase {
+class FocalPointEffectsTest extends KernelTestBase {
+
+  public static $modules = ['crop', 'user', 'image'];
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installEntitySchema('crop');
+    $this->installEntitySchema('user');
+  }
 
   /**
    * @dataProvider calculateResizeDataProvider
@@ -45,44 +58,38 @@ class FocalPointEffectsTest extends UnitTestCase {
   }
 
   /**
-   * @dataProvider calculateCropDataProvider
+   * @dataProvider calculateAnchorProvider
    */
-  public function testCalculateCropData($focal_point, $image_width, $image_height, $crop_width, $crop_height, $expected) {
-    $this->assertSame($expected, FocalPointEffectBase::calculateCropData($focal_point, $image_width, $image_height, $crop_width, $crop_height));
-  }
+  public function testCalculateAnchor($image_size, $crop_size, $focal_point_offset, $expected) {
+    $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->disableOriginalConstructor()->getMock();
+    $crop_storage = $this->getMockBuilder('Drupal\crop\CropStorageInterface')->disableOriginalConstructor()->getMock();
+    $immutable_config = $this->getMockBuilder('Drupal\Core\Config\ImmutableConfig')->disableOriginalConstructor()->getMock();
 
-  /**
-   * Data provider for testCalculateCropData().
-   *
-   * @see FocalPointEffectsTest::testCalculateCropData()
-   */
-  public function calculateCropDataProvider() {
-    return array(
-      array('50,50', 640, 480, 300, 100, array('width' => 300, 'height' => 100, 'x' => 170, 'y' => 190)),
-      array('50,50', 640, 480, 100, 300, array('width' => 100, 'height' => 300, 'x' => 270, 'y' => 90)),
-      array('50,50', 480, 640, 300, 100, array('width' => 300, 'height' => 100, 'x' => 90, 'y' => 270)),
-      array('50,50', 480, 640, 100, 300, array('width' => 100, 'height' => 300, 'x' => 190, 'y' => 170)),
-      array('50,50', 1920, 1080, 400, 300, array('width' => 400, 'height' => 300, 'x' => 760, 'y' => 390)),
+    $effect = new FocalPointCropImageEffect([], 'plugin_id', [], $logger, $crop_storage, $immutable_config);
 
-      array('invalid', 640, 480, 300, 100, array('width' => 300, 'height' => 100, 'x' => 170, 'y' => 190)),
-      array('invalid', 640, 480, 100, 300, array('width' => 100, 'height' => 300, 'x' => 270, 'y' => 90)),
-      array('invalid', 480, 640, 300, 100, array('width' => 300, 'height' => 100, 'x' => 90, 'y' => 270)),
-      array('invalid', 480, 640, 100, 300, array('width' => 100, 'height' => 300, 'x' => 190, 'y' => 170)),
-      array('invalid', 1920, 1080, 400, 300, array('width' => 400, 'height' => 300, 'x' => 760, 'y' => 390)),
+    $image = $this->getMockBuilder('Drupal\Core\Image\ImageInterface')->disableOriginalConstructor()->getMock();
+    $image->expects($this->once())
+      ->method('getWidth')
+      ->will($this->returnValue($image_size[0]));
+    $image->expects($this->once())
+      ->method('getHeight')
+      ->will($this->returnValue($image_size[1]));
 
-      array('75,25', 640, 480, 300, 100, array('width' => 300, 'height' => 100, 'x' => 330, 'y' => 70)),
-      array('75,25', 640, 480, 100, 300, array('width' => 100, 'height' => 300, 'x' => 430, 'y' => 0)),
-      array('75,25', 480, 640, 300, 100, array('width' => 300, 'height' => 100, 'x' => 180, 'y' => 110)),
-      array('75,25', 480, 640, 100, 300, array('width' => 100, 'height' => 300, 'x' => 310, 'y' => 10)),
-      array('75,25', 1920, 1080, 400, 300, array('width' => 400, 'height' => 300, 'x' => 1240, 'y' => 120)),
-    );
-  }
+    $crop = Crop::create([
+      'type' => 'fake_type',
+      'entity_id' => 1,
+      'entity_type' => 'file',
+      'uri' => 'public://fake.jpg',
+      'x' => $focal_point_offset[0],
+      'y' => $focal_point_offset[1],
+      'width' => $crop_size[0],
+      'height' => $crop_size[1],
+    ]);
 
-  /**
-   * @dataProvider calculateAnchorProvider
-   */
-  public function testCalculateAnchor($image_size, $crop_size, $focal_point_offset, $expected) {
-    $this->assertSame($expected, FocalPointEffectBase::calculateAnchor($image_size, $crop_size, $focal_point_offset));
+    $effect_reflection = new \ReflectionClass('Drupal\focal_point\Plugin\ImageEffect\FocalPointCropImageEffect');
+    $method = $effect_reflection->getMethod('calculateAnchor');
+    $method->setAccessible(TRUE);
+    $this->assertSame($expected, $method->invokeArgs($effect, [$image, $crop]));
   }
 
   /**
@@ -91,13 +98,12 @@ class FocalPointEffectsTest extends UnitTestCase {
    * @see FocalPointEffectsTest::testCalculateAnchor()
    */
   public function calculateAnchorProvider() {
-    return array(
-      array(640, 300, 50, 170),
-      array(640, 300, 80, 340),
-      array(640, 300, 10, 0),
-      array(640, 640, 640, 0),
-      array(640, 800, 50, 0),
-    );
+    return [
+      [[640, 640], [300, 300], [320, 320], [170, 170]],
+      [[640, 640], [300, 300], [50, 80], [0, 0]],
+      [[640, 640], [300, 300], [10, 10], [0, 0]],
+      [[640, 640], [640, 800], [640, 50], [0, 0]],
+    ];
   }
 
 }
diff --git a/tests/src/Unit/FocalPointTest.php b/tests/src/Unit/FocalPointTest.php
index 3ca6ffe..8b51ff4 100644
--- a/tests/src/Unit/FocalPointTest.php
+++ b/tests/src/Unit/FocalPointTest.php
@@ -7,66 +7,74 @@
 
 namespace Drupal\Tests\focal_point\Unit;
 
+use Drupal\focal_point\Plugin\Field\FieldWidget\FocalPointImageWidget;
 use Drupal\Tests\UnitTestCase;
-use Drupal\focal_point\FocalPoint;
 
 /**
- * @coversDefaultClass \Drupal\focal_point\FocalPoint
+ * @coversDefaultClass \Drupal\focal_point\Plugin\Field\FieldWidget\FocalPointImageWidget
  * @group Focal Point
  */
 class FocalPointTest extends UnitTestCase {
 
   /**
-   * Tests the parse() method.
-   *
-   * @dataProvider providerParseFocalPoint
+   * {@inheritdoc}
    */
-  public function testFocalPointParse($focal_point, $expected) {
-    $this->assertSame($expected, FocalPoint::parse($focal_point));
-  }
+  protected function setUp() {
+    parent::setUp();
+    $translation_manager = $this->getMockBuilder('Drupal\Core\StringTranslation\TranslationManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $translation_manager->expects($this->any())
+      ->method('translate')
+      ->with($this->anything(), $this->anything())
+      ->will($this->returnValue('Translated string.'));
 
-  /**
-   * Data provider for testFocalPoint().
-   */
-  public function providerParseFocalPoint() {
-    return array(
-      array('23,56', array('x-offset' => '23', 'y-offset' => '56')),
-      array('56,23', array('x-offset' => '56', 'y-offset' => '23')),
-      array('0,0', array('x-offset' => '0', 'y-offset' => '0')),
-      array('100,100', array('x-offset' => '100', 'y-offset' => '100')),
-      array('', array('x-offset' => '50', 'y-offset' => '50')),
-      array('invalid', array('x-offset' => '50', 'y-offset' => '50')),
-    );
+    $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
+    $container->expects($this->any())
+      ->method('get')
+      ->with('string_translation')
+      ->will($this->returnValue($translation_manager));
+
+    \Drupal::setContainer($container);
   }
 
   /**
-   * Tests the validate() method.
+   * Tests the validateFocalPoint() method.
    *
    * @dataProvider providerValidateFocalPoint
    */
-  public function testFocalPointValidate($focal_point, $expected) {
-    $this->assertSame($expected, FocalPoint::validate($focal_point));
+  public function testFocalPointValidate($value, $expected) {
+    $element = ['#parents' => ['field_name'], '#title' => 'Title'];
+    $form_state = $this->getMock('Drupal\Core\Form\FormStateInterface');
+    $form_state->expects($this->any())
+      ->method('getValue')
+      ->with('field_name')
+      ->will($this->returnValue($value));
+    $form_state->expects($expected ? $this->never() : $this->once())
+      ->method('setError');
+
+    FocalPointImageWidget::validateFocalPoint($element, $form_state);
   }
 
   /**
    * Data provider for testFocalPoint().
    */
   public function providerValidateFocalPoint() {
-    return array(
-      array('50,50', TRUE),
-      array('75,25', TRUE),
-      array('3,50', TRUE),
-      array('83,6', TRUE),
-      array('2,9', TRUE),
-      array('100,100', TRUE),
-      array('0,0', TRUE),
-      array('100,0', TRUE),
-      array('-20,50', FALSE),
-      array('18,-3', FALSE),
-      array('44,101', FALSE),
-      array('', FALSE),
-      array('invalid', FALSE),
-    );
+    return [
+      ['50,50', TRUE],
+      ['75,25', TRUE],
+      ['3,50', TRUE],
+      ['83,6', TRUE],
+      ['2,9', TRUE],
+      ['100,100', TRUE],
+      ['0,0', TRUE],
+      ['100,0', TRUE],
+      ['-20,50', FALSE],
+      ['18,-3', FALSE],
+      ['44,101', FALSE],
+      ['', FALSE],
+      ['invalid', FALSE],
+    ];
   }
 
 }
