private function morph($input, callable $callback, $ignore = '/^(data:|https?:|\\/)/') {
        // define regex
        $relativeRegexes = [
            '/url\(\s*(?P<quotes>["\'])?(?P<path>.+?)(?(quotes)(?P=quotes))\s*\)/ix',
            '/@import\s+(?P<quotes>["\'])(?P<path>.+?)(?P=quotes)/ix',
        ];

        // find all relative urls in css
        $matches = [];
        foreach($relativeRegexes as $relativeRegex)
            if(preg_match_all($relativeRegex, $input, $regexMatches, PREG_SET_ORDER))
                $matches = array_merge($matches, $regexMatches);

        // start empty
        $search = [];
        $replace = [];

        // loop all urls
        foreach ($matches as $match) {
            // determine if it's a url() or an @import match
            $type = (strpos($match[0], '@import') === 0 ? 'import' : 'url');
            $url = $match['path'];

            // ignore transformation?
            if(preg_match($ignore, $url) === 0) {
                // attempting to interpret GET-params makes no sense, so let's discard them for awhile
                $params = strrchr($url, '?');
                $url = $params ? substr($url, 0, -strlen($params)) : $url;

                // fix relative url
                $url = $callback($url);

                // now that the path has been converted, re-apply GET-params
                $url .= $params;
            }

            // urls with control characters above 0x7e should be quoted.
            $url = trim($url);
            if(preg_match('/[\s\)\'"#\x{7f}-\x{9f}]/u', $url))
                $url = $match['quotes'] . $url . $match['quotes'];

            // build replacement
            $search[] = $match[0];
            if($type === 'url')
                $replace[] = 'url('.$url.')';
            elseif($type === 'import')
                $replace[] = '@import "'.$url.'"';
        }

        // return replaced input
        return str_replace($search, $replace, $input);
    }