public function manipulate($variant, $callback)
    {
        // Verify this manipulation is applicable to this instance
        if (!$this->exists()) {
            return null;
        }

        // Build output tuple
        $filename = $this->getFilename();
        $hash = $this->getHash();
        $existingVariant = $this->getVariant();
        if ($existingVariant) {
            $variant = $existingVariant . '_' . $variant;
        }

        // Skip empty files (e.g. Folder does not have a hash)
        if (empty($filename) || empty($hash)) {
            return null;
        }

        // Create this asset in the store if it doesn't already exist,
        // otherwise use the existing variant
        $store = Injector::inst()->get(AssetStore::class);
        $tuple = $manipulationResult = null;
        if (!$store->exists($filename, $hash, $variant)) {
            // Circumvent generation of thumbnails if we only want to get existing ones
            if (!$this->getAllowGeneration()) {
                return null;
            }

            $result = call_user_func($callback, $store, $filename, $hash, $variant);

            // Preserve backward compatibility
            if (isset($result['Filename'])) {
                $tuple = $result;
                Deprecation::notice(
                    '5.0',
                    'Closure passed to ImageManipulation::manipulate() should return null or a two-item array 
                        containing a tuple and an image backend, i.e. [$tuple, $result]',
                    Deprecation::SCOPE_GLOBAL
                );
            } else {
                list($tuple, $manipulationResult) = $result;
            }
        } else {
            $tuple = array(
                'Filename' => $filename,
                'Hash' => $hash,
                'Variant' => $variant
            );
        }

        // Callback may fail to perform this manipulation (e.g. resize on text file)
        if (!$tuple) {
            return null;
        }

        // Store result in new DBFile instance
        /** @var DBFile $file */
        $file = DBField::create_field('DBFile', $tuple);

        // Pass the manipulated image backend down to the resampled image - this allows chained manipulations
        // without having to re-load the image resource from the manipulated file written to disk
        if ($manipulationResult instanceof Image_Backend) {
            $file->setImageBackend($manipulationResult);
        }

        return $file->setOriginal($this);
    }