postcss-resemble-image icon indicating copy to clipboard operation
postcss-resemble-image copied to clipboard

No logic for gradient angle?

Open downhiller opened this issue 7 years ago • 10 comments

This only works on images like the one you demonstrate. Have you put any thought into somehow detecting the "best fit" angle to generate a gradient? An issue rather than a pull request as I can't get my head around how to do it I'm afraid..!

downhiller avatar Nov 10 '16 14:11 downhiller

Hi @downhiller,

I'd like to implement a vertical pass as well as the current horizontal one. I like the idea of being able to determine the best rough approximation but I'm not sure how we could go about doing that! Happy to hear any suggestions. 😄

ben-eb avatar Nov 10 '16 14:11 ben-eb

Maybe by rough estimation what would be color difference between edges (left vs right, top vs bottom) and choose bigger difference?

ertrzyiks avatar Nov 10 '16 14:11 ertrzyiks

I'm working on it now... playing around with resampling image down to a small grid (2x2, 3x3, 5x5 etc) and then comparing opposite pixels... it's going to take more than that but it seemed a good starting point.

downhiller avatar Nov 10 '16 15:11 downhiller

I've got something very basic that probably does enough for my needs. It simply converts a file to a 5x5 grid, then compares every edge pixel to the diametrically-opposed one. Gets the pixel colours, converts to Lab, calculate the delta E distance between them. Then I simply grab the start/end colours and angle corresponding to the largest delta E. Moreover I just ran it on 1,000 128x128 thumbnails and it took 2 seconds which seems ok. Not suitable for a PR (it's in PHP for starters), but I might put it up once I've tweaked it a bit.

downhiller avatar Nov 10 '16 16:11 downhiller

@downhiller That sounds great!

ben-eb avatar Nov 10 '16 16:11 ben-eb

Would we then have to get triangular and parallelogram chunks of the original image?

Lorti avatar Nov 10 '16 16:11 Lorti

I don't follow Lorti?

downhiller avatar Nov 10 '16 17:11 downhiller

Sorry, maybe I was thinking too far ahead. If you can calculate the best angle we should also split the image along that axis. Right now we create n rectangles along the horizontal axis and calculate the color for each rectangle. As soon as we detect the best angle we should ideally split the image along that axis, which adds a bit of complexity.

Lorti avatar Nov 10 '16 17:11 Lorti

@downhiller Would you be able to share what you have so far? I might be able to have a go at porting it to JS. 😄

ben-eb avatar Nov 16 '16 12:11 ben-eb

It's VERY basic, and it's not pretty, as I just hacked it together while working on other things, but here it is.

I stripped out my implementation-specific stuff so apologies if I've introduced a bug. In my case I was pulling in filenames from a database and looping through the recordset and then updating the DB row with the generated gradient. (For compactness I just store it as $ang,$start,$end - just the three data points, no need to store the syntax of the linear-gradient rule. I shortened the colours to the closest three-character (#a2bec4 -> #9bb or whatever) as well to save on DB space and HTML size.

My thumbs are all square 128x128 files as well, so I didn't have to consider rectangular dimensions.

The required class file is https://github.com/mtias/tonesque/blob/master/class.color.php (not my work)

<?php

require('class.color.php');

$filename = 'yourfilename.jpg here' // pull in a file from somewhere

echo '<img src="'.$filename.'">';

$img = new Imagick($filename);

$chunks = 5;

$img->resizeImage($chunks, $chunks, Imagick::FILTER_LANCZOS, 1);

echo '<img style="image-rendering:optimizeSpeed;image-rendering:-webkit-optimize-contrast;image-rendering: pixelated;" width="128" height="128" src="data:image/jpeg;base64,'.base64_encode($img).'">';

$maxdiff = 0;
$start   = '';
$end     = '';
$ang     = '';
$debug   = '';

// first iterate along the top edge, and then down the side
for ($rotated = 0; $rotated <= 1; $rotated++) {
    // use +- $rotated here to offset the loop so we're not duplicating the corners
    for ($i = 0 + $rotated; $i < $chunks - $rotated; $i++) {
        // on first run pix1 will be top-left, pix2 will be diagonally opposite, bottom-right
        if ($rotated) {
            $pix1 = [0, $i];
            $pix2 = [$chunks - 1, $chunks - 1 - $i];
        } else {
            $pix1 = [$i, 0];
            $pix2 = [$chunks - 1 - $i, $chunks - 1];
        }

        // get imagick color objects
        $col1 = $img->getImagePixelColor($pix1[0], $pix1[1])->getColor();
        $col2 = $img->getImagePixelColor($pix2[0], $pix2[1])->getColor();

        // convert to format that we can do lab distance on
        $col1 = (new Color($col1, 'rgba'));
        $col2 = (new Color($col2, 'rgba'));

        // get delta E
        $diff = $col1->getDistanceLabFrom($col2);
        
        // store the biggest delta E
        if ($diff > $maxdiff) {
            $maxdiff = $diff;
            $start = hexToShort($col1->toCSS());
            $end = hexToShort($col2->toCSS());
            $ang = getDegrees($pix1[0], $pix1[1], $pix2[0], $pix2[1]);
        }

        // just some debug output to let you see what's happened
        $debug .= '<div style="width:20px;height:20px;background:'.hexToShort($col1->toCSS()).'"></div>'
                . '<div style="width:20px;height:20px;background:'.hexToShort($col2->toCSS()).'"></div>'
                . '<br>'.$diff
                . '<br>'.getDegrees($pix1[0], $pix1[1], $pix2[0], $pix2[1])
                . '<hr>';
    }
}

$img->clear();

echo $debug;

echo $ang;
echo $start;
echo $end;




function getDegrees(int $x1, int $y1, int $x2, int $y2): int
{
    $y1 *= -1;
    $y2 *= -1;

    $adj = ($y2 - $y1);
    $opp = ($x2 - $x1);

    $ang = atan2($opp, $adj) * 180 / M_PI;

    return (int) round(($ang + 360) % 360);
}



function hexToShort(string $hex): string
{
    // if it is short, return
    if (mb_strlen($hex) === 4) {
        return $hex;
    }

    // remove #
    if (mb_substr($hex, 0, 1) === '#') {
        $hex = mb_substr($hex, 1);
    }

    // check that hex is valid
    if (mb_strlen($hex) !== 6) {
        return '';
    }

    $r = mb_substr($hex, 0, 2);
    $g = mb_substr($hex, 2, 2);
    $b = mb_substr($hex, 4, 2);
    
    return shortVal($r) . shortVal($g) . shortVal($b);
}




function shortVal(string $c): string
{
    // get integer representation
    $i = hexdec($c);

    // do some magic
    $s = ($i % 17 > 7) ? (17 + $i - $i % 17) : ($i - $i % 17);

    // back to hex
    $s = (string) dechex($s);

    return mb_strtolower($s[0]);
}

downhiller avatar Nov 21 '16 10:11 downhiller