diff --git a/README.md b/README.md
index abd9f43..ac4e966 100644
--- a/README.md
+++ b/README.md
@@ -16,9 +16,8 @@ Currently, Focal Point integrates with the standard image fields.
 ##DEPENDENCIES
 
 - image
-- crop
 
-##USAGE
+##USUAGE
 
 ### Setting up image fields
 
diff --git a/config/install/crop.type.focal_point.yml b/config/install/crop.type.focal_point.yml
deleted file mode 100644
index a78d87d..0000000
--- a/config/install/crop.type.focal_point.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-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
deleted file mode 100644
index 947a6ce..0000000
--- a/config/install/focal_point.settings.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-crop_type: 'focal_point'
-default_value: '50,50'
-
diff --git a/config/schema/focal_point.schema.yml b/config/schema/focal_point.schema.yml
deleted file mode 100644
index bee70d9..0000000
--- a/config/schema/focal_point.schema.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-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 49e4d4c..e5abb55 100644
--- a/focal_point.info.yml
+++ b/focal_point.info.yml
@@ -6,4 +6,5 @@ package: Images
 version: VERSION
 dependencies:
   - image
-  - crop (>=8.x-1.0-alpha2)
+test_dependencies:
+  - crop
diff --git a/focal_point.install b/focal_point.install
index cf389eb..736cd6f 100644
--- a/focal_point.install
+++ b/focal_point.install
@@ -5,126 +5,37 @@
  * Install hooks for focal_point.
  */
 
-/**
- * Checks if required version of Crop API is installed.
- *
- * @return bool
- *   TRUE if dependency is met and FALSE if not.
- */
-function _focal_point_check_crop_version() {
-  if (\Drupal::moduleHandler()->moduleExists('crop')) {
-    $info = system_get_info('module', 'crop');
-    if (version_compare($info['version'], '8.x-1.0-alpha2') >= 0) {
-      return TRUE;
-    }
-  }
-
-  return FALSE;
-}
-
-/**
- * Implements hook_requirements().
- */
-function focal_point_requirements($phase) {
-  $requirements = [];
-  if ($phase == 'update' && !_focal_point_check_crop_version()) {
-    $requirements = [
-      '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,
-      ],
-    ];
-  }
-  return $requirements;
-}
 
 /**
- * Install default config.
+ * Implements hook_uninstall().
  */
-function focal_point_update_8001() {
-  if (!_focal_point_check_crop_version()) {
-    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.');
-  }
+function focal_point_uninstall() {
 
-  \Drupal::service('config.installer')
-    ->installDefaultConfig('module', 'focal_point');
 }
 
 /**
- * Migrates legacy values to crop entities.
+ * Implements hook_schema().
  */
-function focal_point_update_8002(&$sandbox) {
-  /** @var \Drupal\file\FileUsage\FileUsageInterface $file_usage */
-  $file_usage = \Drupal::service('file.usage');
-  $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);
-
-    // Try to load width and height from the image fields and fall back to
-    // reading image file if that was not successful.
-    $usage = $file_usage->listUsage($file);
-    $usage = current($usage);
-
-    /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
-    $entity = \Drupal::entityTypeManager()->getStorage(key($usage))->load(current(current($usage)));
-    $size = NULL;
-    foreach ($entity->getFieldDefinitions() as $field_definition) {
-      if ($field_definition->getType() == 'image') {
-        foreach ($entity->{$field_definition->getName()} as $item) {
-          if ($item->target_id == $file->id()) {
-            $size = [$item->width, $item->height];
-          }
-        }
-      }
-    }
-
-    if (empty($size)) {
-      $size = getimagesize($file->getFileUri());
-    }
-
-    // Now we have all information we need. Let's create crop entity.
-    $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['total_items'] ? $sandbox['num_processed'] / $sandbox['total_items'] : 1;
-
-  // Intentionally leaving legacy table. You never know...
+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;
 }
diff --git a/focal_point.module b/focal_point.module
index d56ab61..221b06f 100644
--- a/focal_point.module
+++ b/focal_point.module
@@ -11,7 +11,7 @@
  */
 
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\crop\Entity\Crop;
+use Drupal\focal_point\FocalPoint;
 
 /**
  * Implements hook_entity_presave().
@@ -23,14 +23,12 @@ 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->hasField($value->getName())) {
+      if ($value->getType() == 'image' && $entity->{$value->getName()} !== NULL) {
         // Loop through all values for this field. Its cardinality might be > 1.
         foreach ($entity->{$value->getName()} as $item) {
           if (isset($item->focal_point)) {
-            list($x, $y) = explode(',', $item->focal_point);
-            $crop_type = \Drupal::config('focal_point.settings')->get('crop_type');
-            $crop = \Drupal::service('focal_point.manager')->getCropEntity($item->entity, $crop_type);
-            \Drupal::service('focal_point.manager')->saveCropEntity($x, $y, $item->width, $item->height, $crop);
+            $focal_point = new FocalPoint($item->target_id);
+            $focal_point->saveFocalPoint($item->focal_point);
           }
         }
       }
diff --git a/focal_point.services.yml b/focal_point.services.yml
deleted file mode 100644
index 6fc7200..0000000
--- a/focal_point.services.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-services:
-  focal_point.manager:
-    class: Drupal\focal_point\FocalPointManager
-    arguments: ['@entity_type.manager']
diff --git a/src/FocalPoint.php b/src/FocalPoint.php
new file mode 100644
index 0000000..5d2fbb7
--- /dev/null
+++ b/src/FocalPoint.php
@@ -0,0 +1,211 @@
+<?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 d86592b..b1cdae2 100644
--- a/src/FocalPointEffectBase.php
+++ b/src/FocalPointEffectBase.php
@@ -1,90 +1,29 @@
 <?php
 
+/**
+ * @file
+ * Contains \Drupal\focal_point\FocalPointEffectBase.
+ */
+
 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\crop\Entity\Crop;
 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 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 $focal_point_config
-   *   Focal point configuration object.
-   */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, LoggerInterface $logger, CropStorageInterface $crop_storage, ImmutableConfig $focal_point_config) {
-    parent::__construct($configuration, $plugin_id, $plugin_definition, $logger);
-    $this->cropStorage = $crop_storage;
-    $this->focalPointConfig = $focal_point_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')
-    );
-  }
-
+abstract class FocalPointEffectBase extends ResizeImageEffect {
   /**
-   * Calculate the resize dimensions of an image.
-   *
-   * The calculated dimensions are based on the longest crop dimension (length
-   * or width) so that the aspect ratio is preserved in all cases and that there
-   * is always enough image available to the crop.
+   * 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
+   * enough image available to the crop.
    *
    * @param int $image_width
-   *   Image width.
    * @param int $image_height
-   *   Image height.
    * @param int $crop_width
-   *   Crop width.
    * @param int $crop_height
-   *   Crop height.
    *
-   * @return array $resize_data
-   *   Resize data.
+   * @return array
    */
   public static function calculateResizeData($image_width, $image_height, $crop_width, $crop_height) {
     $resize_data = array();
@@ -114,91 +53,64 @@ abstract class FocalPointEffectBase extends ResizeImageEffect implements Contain
   }
 
   /**
-   * Applies the crop effect to an image.
+   * Compile the necessary data for the image crop effect.
    *
-   * @param ImageInterface $image
-   *   The image resource to crop.
+   * @param string $focal_point
+   * @param int $image_width
+   * @param int $image_height
+   * @param int $crop_width
+   * @param int $crop_height
    *
-   * @return bool
-   *   TRUE if the image is successfully cropped, otherwise FALSE.
+   * @return array|bool
+   *   An array containing the following keys:
+   *    - width
+   *    - height
+   *    - x
+   *    - y
    */
-  public function applyCrop(ImageInterface $image) {
-    $crop_type = $this->focalPointConfig->get('crop_type');
-
-    /** @var \Drupal\crop\CropInterface $crop */
-    if ($crop = Crop::findCrop($image->getSource(), $crop_type)) {
-      // An existing crop has been found; set the size.
-      $crop->setSize($this->configuration['width'], $this->configuration['height']);
-    }
-    else {
-      // No existing crop could be found; create a new one using the size.
-      $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;
-    }
-
-    return TRUE;
+  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 top left coordinates of crop rectangle.
+   * Calculate the anchor offset for the given dimension.
    *
-   * This is based on Crop's anchor function with additional logic to ensure
-   * that crop area doesn't fall outside of the original image. Note that the
-   * image modules crop effect expects the top left coordinate of the crop
-   * rectangle.
+   * @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
-   *   Crop entity.
-   *
-   * @return array
-   *   Array with two keys (x, y) and anchor coordinates as values.
+   * @return int
    */
-  protected function calculateAnchor(ImageInterface $image, CropInterface $crop) {
-    $original_anchor = $crop->anchor();
-    $new_anchor = $original_anchor;
-
-    $image_size = [
-      'width' => $image->getWidth(),
-      'height' => $image->getHeight(),
-    ];
-
-    // Set the minimum number of pixels that must exist between the edge of the
-    // image and the anchor point (in both the x and y directions).
-    $crop_size = $crop->size();
-    $anchor_min_margin_x = (int) ceil($crop_size['width'] / 2);
-    $anchor_min_margin_y = (int) ceil($crop_size['height'] / 2);
-
-    // Ensure that the crop area doesn't fall off the bottom right of the image.
-    $new_anchor['x'] = ($original_anchor['x'] + $anchor_min_margin_x > $image_size['width']) ? $image_size['width'] - $crop_size['width'] : $original_anchor['x'] - $anchor_min_margin_x;
-    $new_anchor['y'] = ($original_anchor['y'] + $anchor_min_margin_y > $image_size['height']) ? $image_size['height'] - $crop_size['height'] : $original_anchor['y'] - $anchor_min_margin_y;
-
-    // Ensure that the crop area doesn't fall off the top left of the image.
-    $new_anchor['x'] = max(0, $new_anchor['x']);
-    $new_anchor['y'] = max(0, $new_anchor['y']);
-
-    return $new_anchor;
+  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;
   }
 
 }
diff --git a/src/FocalPointManager.php b/src/FocalPointManager.php
deleted file mode 100644
index 4bbd66a..0000000
--- a/src/FocalPointManager.php
+++ /dev/null
@@ -1,107 +0,0 @@
-<?php
-
-namespace Drupal\focal_point;
-
-use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\crop\CropInterface;
-use Drupal\crop\Entity\Crop;
-use Drupal\file\FileInterface;
-
-/**
- * Provides business logic related to focal point.
- */
-class FocalPointManager implements FocalPointManagerInterface {
-
-  /**
-   * Regular expression for focal point form value validation.
-   *
-   * @var string
-   */
-  const VALIDATION_REGEXP = '/^(100|[0-9]{1,2})(,)(100|[0-9]{1,2})$/';
-
-  /**
-   * Crop entity storage.
-   *
-   * @var \Drupal\crop\CropStorageInterface
-   */
-  protected $cropStorage;
-
-  /**
-   * Constructs FocalPointManager.
-   *
-   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
-   *   Entity type manager service.
-   */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
-    $this->cropStorage = $entity_type_manager->getStorage('crop');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function validateFocalPoint($focal_point) {
-    return (bool) preg_match(static::VALIDATION_REGEXP, $focal_point);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function relativeToAbsolute($x, $y, $width, $height) {
-    // Focal point JS provides relative location while crop entity
-    // expects exact coordinate on the original image. Let's convert.
-    return [
-      'x' => (int) round(($x / 100.0) * $width),
-      'y' => (int) round(($y / 100.0) * $height),
-    ];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function absoluteToRelative($x, $y, $width, $height) {
-    return [
-      'x' => (int) round($x / $width * 100),
-      'y' => (int) round($y / $height * 100),
-    ];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getCropEntity(FileInterface $file, $crop_type) {
-    if (Crop::cropExists($file->getFileUri(), $crop_type)) {
-      /** @var \Drupal\crop\CropInterface $crop */
-      $crop = Crop::findCrop($file->getFileUri(), $crop_type);
-    }
-    else {
-      $values = [
-        'type' => $crop_type,
-        'entity_id' => $file->id(),
-        'entity_type' => 'file',
-        'uri' => $file->getFileUri(),
-      ];
-
-      $crop = \Drupal::entityTypeManager()
-        ->getStorage('crop')
-        ->create($values);
-    }
-
-    return $crop;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function saveCropEntity($x, $y, $width, $height, CropInterface $crop) {
-    $absolute = $this->relativeToAbsolute($x, $y, $width, $height);
-
-    $anchor = $crop->anchor();
-    if ($anchor['x'] != $absolute['x'] || $anchor['y'] != $absolute['y']) {
-      $crop->setPosition($absolute['x'], $absolute['y']);
-      $crop->save();
-    }
-
-    return $crop;
-  }
-
-}
diff --git a/src/FocalPointManagerInterface.php b/src/FocalPointManagerInterface.php
deleted file mode 100644
index 06c1c05..0000000
--- a/src/FocalPointManagerInterface.php
+++ /dev/null
@@ -1,98 +0,0 @@
-<?php
-
-namespace Drupal\focal_point;
-
-use Drupal\file\FileInterface;
-use Drupal\crop\CropInterface;
-
-/**
- * Defines an interface for focal point manager.
- */
-interface FocalPointManagerInterface {
-
-  /**
-   * Validates focal point string representation.
-   *
-   * @param string $focal_point
-   *   Focal point as submitted in the form.
-   *
-   * @return bool
-   *   TRUE if valid and FALSE if not.
-   */
-  public function validateFocalPoint($focal_point);
-
-  /**
-   * Converts relative focal point coordinates to absolute.
-   *
-   * @param float $x
-   *   X coordinate of the focal point.
-   * @param float $y
-   *   Y coordinate of the focal point.
-   * @param int $width
-   *   Width of the original image.
-   * @param int $height
-   *   Height of the original image.
-   *
-   * @return array
-   *   Array containing absolute coordinates of the focal point. 'x' and 'y' are
-   *   used for array keys and corresponding coordinates as values.
-   *
-   * @see absoluteToRelative
-   */
-  public function relativeToAbsolute($x, $y, $width, $height);
-
-  /**
-   * Converts absolute focal point coordinates to relative.
-   *
-   * @param int $x
-   *   X coordinate of the focal point.
-   * @param int $y
-   *   Y coordinate of the focal point.
-   * @param int $width
-   *   Width of the original image.
-   * @param int $height
-   *   Height of the original image.
-   *
-   * @return array
-   *   Array containing relative coordinates of the focal point. 'x' and 'y' are
-   *   used for array keys and corresponding coordinates as values.
-   *
-   * @see relativeToAbsolute
-   */
-  public function absoluteToRelative($x, $y, $width, $height);
-
-  /**
-   * Gets a crop entity for the given file.
-   *
-   * If an existing crop entity is not found then a new one is created.
-   *
-   * @param \Drupal\file\FileInterface $file
-   *   File this focal point applies to.
-   * @param string $crop_type
-   *   Crop type to be used.
-   *
-   * @return \Drupal\crop\CropInterface
-   *   Created crop entity.
-   */
-  public function getCropEntity(FileInterface $file, $crop_type);
-
-  /**
-   * Converts relative focal point coordinates as a crop entity.
-   *
-   * @param float $x
-   *   X coordinate of the focal point.
-   * @param float $y
-   *   Y coordinate of the focal point.
-   * @param int $width
-   *   Width of the original image.
-   * @param int $height
-   *   Height of the original image.
-   * @param \Drupal\crop\CropInterface $crop
-   *   Crop entity for the given file.
-   *
-   * @return \Drupal\crop\CropInterface
-   *   Saved crop entity.
-   */
-  public function saveCropEntity($x, $y, $width, $height, CropInterface $crop);
-
-}
diff --git a/src/Plugin/Field/FieldWidget/FocalPointImageWidget.php b/src/Plugin/Field/FieldWidget/FocalPointImageWidget.php
index 5ee8830..7ac4305 100644
--- a/src/Plugin/Field/FieldWidget/FocalPointImageWidget.php
+++ b/src/Plugin/Field/FieldWidget/FocalPointImageWidget.php
@@ -7,8 +7,7 @@
 
 namespace Drupal\focal_point\Plugin\Field\FieldWidget;
 
-use Drupal\Core\StringTranslation\TranslatableMarkup;
-use Drupal\crop\Entity\Crop;
+use Drupal\focal_point\FocalPoint;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\image\Plugin\Field\FieldWidget\ImageWidget;
 
@@ -67,8 +66,8 @@ class FocalPointImageWidget extends ImageWidget {
     $element['focal_point'] = array(
       '#type' => 'textfield',
       '#title' => 'Focal point',
-      '#description' => new TranslatableMarkup('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'] : \Drupal::config('focal_point.settings')->get('default_value'),
+      '#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,
       '#element_validate' => array('\Drupal\focal_point\Plugin\Field\FieldWidget\FocalPointImageWidget::validateFocalPoint'),
       '#attributes' => array(
         'class' => array('focal-point', $element_selector),
@@ -96,17 +95,8 @@ 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'])) {
-      /** @var \Drupal\file\FileInterface $file */
-      $file = \Drupal::service('entity_type.manager')
-        ->getStorage('file')
-        ->load($return['target_id']);
-      $crop_type = \Drupal::config('focal_point.settings')->get('crop_type');
-      $crop = Crop::findCrop($file->getFileUri(), $crop_type);
-      if ($crop) {
-        $anchor = \Drupal::service('focal_point.manager')
-          ->absoluteToRelative($crop->x->value, $crop->y->value, $return['width'], $return['height']);
-        $return['focal_point'] = "{$anchor['x']},{$anchor['y']}";
-      }
+      $element['#focal_point'] = new FocalPoint($return['target_id']);
+      $return['focal_point'] = $element['#focal_point']->getFocalPoint();
     }
     return $return;
   }
@@ -120,8 +110,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) && \Drupal::service('focal_point.manager')->validateFocalPoint($focal_point_value)) {
-      $form_state->setError($element, new TranslatableMarkup('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) && !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'])));
     }
   }
 
diff --git a/src/Plugin/ImageEffect/FocalPointCropImageEffect.php b/src/Plugin/ImageEffect/FocalPointCropImageEffect.php
index 0b4098f..e4211c8 100644
--- a/src/Plugin/ImageEffect/FocalPointCropImageEffect.php
+++ b/src/Plugin/ImageEffect/FocalPointCropImageEffect.php
@@ -7,16 +7,18 @@
 
 namespace Drupal\focal_point\Plugin\ImageEffect;
 
+use Drupal\focal_point\FocalPoint;
 use Drupal\focal_point\FocalPointEffectBase;
 use Drupal\Core\Image\ImageInterface;
 
 /**
- * Crops image while keeping its focal point as close to centered as possible.
+ * Scales and crops an image resource while keeping its focal point as close to
+ * the center of the resulting image as possible.
  *
  * @ImageEffect(
  *   id = "focal_point_crop",
  *   label = @Translation("Focal Point Crop"),
- *   description = @Translation("Crops image while keeping its focal point as close to centered as possible.")
+ *   description = @Translation("Crop the image while keeping its focal point as close to the center of the resulting image as possible.")
  * )
  */
 class FocalPointCropImageEffect extends FocalPointEffectBase {
@@ -25,7 +27,15 @@ class FocalPointCropImageEffect extends FocalPointEffectBase {
    * {@inheritdoc}
    */
   public function applyEffect(ImageInterface $image) {
-    return $this->applyCrop($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()));
+      return FALSE;
+    }
+
+    return TRUE;
   }
 
 }
diff --git a/src/Plugin/ImageEffect/FocalPointScaleAndCropImageEffect.php b/src/Plugin/ImageEffect/FocalPointScaleAndCropImageEffect.php
index 27160ac..97fbba5 100644
--- a/src/Plugin/ImageEffect/FocalPointScaleAndCropImageEffect.php
+++ b/src/Plugin/ImageEffect/FocalPointScaleAndCropImageEffect.php
@@ -7,17 +7,18 @@
 
 namespace Drupal\focal_point\Plugin\ImageEffect;
 
-use Drupal\crop\Entity\Crop;
+use Drupal\focal_point\FocalPoint;
 use Drupal\focal_point\FocalPointEffectBase;
 use Drupal\Core\Image\ImageInterface;
 
 /**
- * Scales and crops image while keeping its focal point close to centered.
+ * Scales and crops an image resource while keeping its focal point as close to
+ * the center of the resulting image as possible.
  *
  * @ImageEffect(
  *   id = "focal_point_scale_and_crop",
  *   label = @Translation("Focal Point Scale and Crop"),
- *   description = @Translation("Scales and crops image while keeping its focal point close to centered.")
+ *   description = @Translation("Scale and crop will maintain the aspect-ratio of the original image, then crop the larger dimension while keeping its focal point as close to the center of the resulting image as possible.")
  * )
  */
 class FocalPointScaleAndCropImageEffect extends FocalPointEffectBase {
@@ -29,20 +30,19 @@ 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'])) {
-      $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(),
-        ]
-      );
+      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);
       return FALSE;
     }
 
     // Next, attempt to crop the image.
-    return $this->applyCrop($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);
+      return FALSE;
+    }
+
+    return TRUE;
   }
 
 }
diff --git a/tests/src/Unit/Effects/FocalPointEffectsTest.php b/tests/src/Unit/Effects/FocalPointEffectsTest.php
index d0c43dd..8f8f0f6 100644
--- a/tests/src/Unit/Effects/FocalPointEffectsTest.php
+++ b/tests/src/Unit/Effects/FocalPointEffectsTest.php
@@ -7,34 +7,20 @@
 
 namespace Drupal\Tests\focal_point\Unit\Effects;
 
-use Drupal\Core\Config\ImmutableConfig;
-use Drupal\Core\Image\ImageInterface;
-use Drupal\crop\CropInterface;
-use Drupal\crop\CropStorageInterface;
-use Drupal\focal_point\Plugin\ImageEffect\FocalPointCropImageEffect;
 use Drupal\Tests\UnitTestCase;
 use Drupal\focal_point\FocalPointEffectBase;
-use Psr\Log\LoggerInterface;
 
 /**
  * Tests the Focal Point image effects.
  *
  * @group Focal Point
+ * @group Drupal
  *
- * @coversDefaultClass \Drupal\focal_point\FocalPointEffectBase
+ * @see \Drupal\focal_point\FocalPointEffectBase
  */
 class FocalPointEffectsTest extends UnitTestCase {
 
   /**
-   * {@inheritdoc}
-   */
-  protected function setUp() {
-    parent::setUp();
-  }
-
-  /**
-   * @covers ::calculateResizeData
-   *
    * @dataProvider calculateResizeDataProvider
    */
   public function testCalculateResizeData($image_width, $image_height, $crop_width, $crop_height, $expected) {
@@ -47,47 +33,56 @@ class FocalPointEffectsTest extends UnitTestCase {
    * @see FocalPointEffectsTest::testCalculateResizeData()
    */
   public function calculateResizeDataProvider() {
-    $data = [];
-    $data['horizontal_image_horizontal_crop'] = [640, 480, 300, 100, ['width' => 300, 'height' => 225]];
-    $data['horizontal_image_vertical_crop'] = [640, 480, 100, 300, ['width' => 400, 'height' => 300]];
-    $data['vertical_image_horizontal_crop'] = [480, 640, 300, 100, ['width' => 300, 'height' => 400]];
-    $data['vertical_image_vertical_crop'] = [480, 640, 100, 300, ['width' => 225, 'height' => 300]];
-    $data['horizontal_image_too_large_crop'] = [640, 480, 3000, 1000, ['width' => 3000, 'height' => 2250]];
-    $data['image_too_narrow_to_crop_after_resize'] = [1920, 1080, 400, 300, ['width' => 533, 'height' => 300]];
-    $data['image_too_short_to_crop_after_resize'] = [200, 400, 1000, 1000, ['width' => 1000, 'height' => 2000]];
-    return $data;
+    return array(
+      array(640, 480, 300, 100, array('width' => 300, 'height' => 225)), // Horizontal image with horizontal crop.
+      array(640, 480, 100, 300, array('width' => 400, 'height' => 300)), // Horizontal image with vertical crop.
+      array(480, 640, 300, 100, array('width' => 300, 'height' => 400)), // Vertical image with horizontal crop.
+      array(480, 640, 100, 300, array('width' => 225, 'height' => 300)), // Vertical image with vertical crop.
+      array(640, 480, 3000, 1000, array('width' => 3000, 'height' => 2250)), // Horizontal image with too large crop.
+      array(1920, 1080, 400, 300, array('width' => 533, 'height' => 300)), // Image would be too narrow to crop after resize.
+      array(200, 400, 1000, 1000, array('width' => 1000, 'height' => 2000)), // Image would be too short to crop after resize.
+    );
   }
 
   /**
-   * @covers ::calculateAnchor
-   *
-   * @dataProvider calculateAnchorProvider
+   * @dataProvider calculateCropDataProvider
    */
-  public function testCalculateAnchor($image_size, $crop_size, $focal_point_anchor, $expected) {
-    $logger = $this->prophesize(LoggerInterface::class);
-    $crop_storage = $this->prophesize(CropStorageInterface::class);
-    $immutable_config = $this->prophesize(ImmutableConfig::class);
+  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));
+  }
 
-    $effect = new FocalPointCropImageEffect([], 'plugin_id', [], $logger->reveal(), $crop_storage->reveal(), $immutable_config->reveal());
+  /**
+   * 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)),
 
-    $image = $this->prophesize(ImageInterface::class);
-    $image->getWidth()->willReturn($image_size[0]);
-    $image->getHeight()->willReturn($image_size[1]);
+      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)),
 
-    $crop = $this->prophesize(CropInterface::class);
-    $crop->anchor()->willReturn([
-      'x' => $focal_point_anchor[0],
-      'y' => $focal_point_anchor[1],
-    ]);
-    $crop->size()->willReturn([
-      'width' => $crop_size[0],
-      'height' => $crop_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)),
+    );
+  }
 
-    $effect_reflection = new \ReflectionClass(FocalPointCropImageEffect::class);
-    $method = $effect_reflection->getMethod('calculateAnchor');
-    $method->setAccessible(TRUE);
-    $this->assertSame($expected, $method->invokeArgs($effect, [$image->reveal(), $crop->reveal()]));
+  /**
+   * @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));
   }
 
   /**
@@ -96,18 +91,13 @@ class FocalPointEffectsTest extends UnitTestCase {
    * @see FocalPointEffectsTest::testCalculateAnchor()
    */
   public function calculateAnchorProvider() {
-    $data = [];
-    $data['crop_fits_within_image_even'] = [[1000, 100], [30, 20], [250, 50], ['x' => 235, 'y' => 40]];
-    $data['crop_fits_within_image_odd'] = [[1000, 100], [35, 25], [313, 49], ['x' => 295, 'y' => 36]];
-    $data['crop_does_not_fall_off_image_top'] = [[1000, 100], [30, 40], [313, 10], ['x' => 298, 'y' => 0]];
-    $data['crop_does_not_fall_off_image_bottom'] = [[1000, 100], [50, 50], [900, 90], ['x' => 875, 'y' => 50]];
-    $data['crop_does_not_fall_off_image_left'] = [[1000, 100], [50, 50], [10, 50], ['x' => 0, 'y' => 25]];
-    $data['crop_does_not_fall_off_image_right'] = [[1000, 100], [50, 50], [975, 60], ['x' => 950, 'y' => 35]];
-    $data['crop_does_not_fall_off_image_top_left'] = [[1000, 100], [50, 50], [0, 0], ['x' => 0, 'y' => 0]];
-    $data['crop_does_not_fall_off_image_top_right'] = [[1000, 100], [50, 50], [1000, 5], ['x' => 950, 'y' => 0]];
-    $data['crop_does_not_fall_off_image_bottom_left'] = [[1000, 100], [75, 75], [0, 100], ['x' => 0, 'y' => 25]];
-    $data['crop_does_not_fall_off_image_bottom_right'] = [[1000, 100], [50, 50], [1000, 100], ['x' => 950, 'y' => 50]];
-    return $data;
+    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),
+    );
   }
 
 }
diff --git a/tests/src/Unit/FocalPointTest.php b/tests/src/Unit/FocalPointTest.php
index ff96c7b..3ca6ffe 100644
--- a/tests/src/Unit/FocalPointTest.php
+++ b/tests/src/Unit/FocalPointTest.php
@@ -7,148 +7,66 @@
 
 namespace Drupal\Tests\focal_point\Unit;
 
-use Drupal\crop\CropStorageInterface;
-use Drupal\Core\Entity\EntityTypeManager;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-use Drupal\focal_point\FocalPointManager;
 use Drupal\Tests\UnitTestCase;
+use Drupal\focal_point\FocalPoint;
 
 /**
- * @coversDefaultClass \Drupal\focal_point\FocalPointManager
- *
+ * @coversDefaultClass \Drupal\focal_point\FocalPoint
  * @group Focal Point
  */
 class FocalPointTest extends UnitTestCase {
 
   /**
-   * Focal point manager.
+   * Tests the parse() method.
    *
-   * @var \Drupal\focal_point\FocalPointManagerInterface
+   * @dataProvider providerParseFocalPoint
    */
-  protected $focalPointManager;
+  public function testFocalPointParse($focal_point, $expected) {
+    $this->assertSame($expected, FocalPoint::parse($focal_point));
+  }
 
   /**
-   * {@inheritdoc}
+   * Data provider for testFocalPoint().
    */
-  protected function setUp() {
-    parent::setUp();
-    $crop_storage = $this->prophesize(CropStorageInterface::class);
-
-    $entity_type_manager = $this->prophesize(EntityTypeManager::class);
-    $entity_type_manager->getStorage('crop')->willReturn($crop_storage);
-
-    $container = $this->prophesize(ContainerInterface::class);
-    $container->get('entity_type.manager')->willReturn($entity_type_manager);
-
-    \Drupal::setContainer($container->reveal());
-
-    $this->focalPointManager = new FocalPointManager(\Drupal::service('entity_type.manager'));
+  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')),
+    );
   }
 
   /**
-   * @covers ::validateFocalPoint
+   * Tests the validate() method.
    *
    * @dataProvider providerValidateFocalPoint
    */
-  public function testFocalPointValidate($value, $expected) {
-    $this->assertEquals($expected, $this->focalPointManager->validateFocalPoint($value));
+  public function testFocalPointValidate($focal_point, $expected) {
+    $this->assertSame($expected, FocalPoint::validate($focal_point));
   }
 
   /**
    * Data provider for testFocalPoint().
    */
   public function providerValidateFocalPoint() {
-    $data = [];
-    $data['default_focal_point_position'] = ['50,50', TRUE];
-    $data['basic_focal_point_position_1'] = ['75,25', TRUE];
-    $data['basic_focal_point_position_2'] = ['3,50', TRUE];
-    $data['basic_focal_point_position_3'] = ['83,6', TRUE];
-    $data['basic_focal_point_position_4'] = ['2,9', TRUE];
-    $data['extreme_focal_point_position_top_right'] = ['100,0', TRUE];
-    $data['extreme_focal_point_position_top_left'] = ['0,0', TRUE];
-    $data['extreme_focal_point_position_bottom_right'] = ['100,100', TRUE];
-    $data['extreme_focal_point_position_bottom_left'] = ['0,100', TRUE];
-    $data['invalid_focal_point_position_negative_x'] = ['-20,50', FALSE];
-    $data['invalid_focal_point_position_negative_y'] = ['18,-3', FALSE];
-    $data['invalid_focal_point_position_out_of_bounds_x'] = ['101,33', FALSE];
-    $data['invalid_focal_point_position_out_of_bounds_y'] = ['44,101', FALSE];
-    $data['invalid_focal_point_position_out_of_bounds_xy'] = ['313,512', FALSE];
-    $data['invalid_focal_point_position_empty'] = ['', FALSE];
-    $data['invalid_focal_point_position_incorrect_format_1'] = ['invalid', FALSE];
-    $data['invalid_focal_point_position_incorrect_format_2'] = ['invalid,invalid', FALSE];
-    $data['invalid_focal_point_position_incorrect_format_3'] = ['23,invalid', FALSE];
-
-    return $data;
-  }
-
-  /**
-   * @covers ::relativeToAbsolute
-   *
-   * @dataProvider providerCoordinates
-   */
-  public function testRelativeToAbsolute($relative, $size, $absolute) {
-    $this->assertEquals(
-      $absolute,
-      $this->focalPointManager
-        ->relativeToAbsolute($relative['x'], $relative['y'], $size['width'], $size['height'])
-    );
-  }
-
-  /**
-   * @covers ::absoluteToRelative
-   *
-   * @dataProvider providerCoordinates
-   */
-  public function testAbsoluteToRelative($relative, $size, $absolute) {
-    $this->assertEquals(
-      $relative,
-      $this->focalPointManager
-        ->absoluteToRelative($absolute['x'], $absolute['y'], $size['width'], $size['height'])
+    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),
     );
   }
 
-  /**
-   * Data provider for testRelativeToAbsolute() and absoluteToRelative().
-   */
-  public function providerCoordinates() {
-    $data = [];
-    $data['top_left'] = [
-      ['x' => 0, 'y' => 0],
-      ['width' => 1000, 'height' => 2000],
-      ['x' => 0, 'y' => 0],
-    ];
-    $data['basic_case_1'] = [
-      ['x' => 25, 'y' => 50],
-      ['width' => 1000, 'height' => 2000],
-      ['x' => 250, 'y' => 1000]
-    ];
-    $data['basic_case_2'] = [
-      ['x' => 50, 'y' => 25],
-      ['width' => 1000, 'height' => 2000],
-      ['x' => 500, 'y' => 500],
-    ];
-    $data['basic_case_3'] = [
-      ['x' => 50, 'y' => 50],
-      ['width' => 1000, 'height' => 2000],
-      ['x' => 500, 'y' => 1000],
-    ];
-    $data['basic_case_4'] = [
-      ['x' => 75, 'y' => 50],
-      ['width' => 1000, 'height' => 2000],
-      ['x' => 750, 'y' => 1000],
-    ];
-    $data['basic_case_5'] = [
-      ['x' => 100, 'y' => 75],
-      ['width' => 1000, 'height' => 2000],
-      ['x' => 1000, 'y' => 1500],
-    ];
-    $data['bottom_right'] = [
-      ['x' => 100, 'y' => 100],
-      ['width' => 1000, 'height' => 2000],
-      ['x' => 1000, 'y' => 2000],
-    ];
-
-    return $data;
-  }
-
 }
