postcss-resemble-image
postcss-resemble-image copied to clipboard
No logic for gradient angle?
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..!
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. 😄
Maybe by rough estimation what would be color difference between edges (left vs right, top vs bottom) and choose bigger difference?
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.
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 That sounds great!
Would we then have to get triangular and parallelogram chunks of the original image?
I don't follow Lorti?
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.
@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. 😄
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]);
}