Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
February 21, 2022 11:34 am GMT

Animate a frame by using a transition between main image colors

A disclaimer right away - this is not really about animating image frames; it's just animating the background behind the image and adding some padding.

I wanted to draw more attention to some images in a grid of uploaded images in one of my projects. Instead of having a plain card design or having to tweak borders and shadows around those images, I came up with this technique.

The idea is to extract the main image colors and compose a gradient with transitions between them. This gradient will "circle around" the image making it look more lively and vibrant.
At the same time, each image will have its own unique animated gradient which, in my opinion, will make the transition between the image, image frame and container background color more seamless.

To achieve that, main image colors are extracted for each image. This "palette" of colors is used in composing the aforementioned gradient.

A couple of examples:

Palette 1

Palette 2

Palette 3

Palette 4

The code was mainly influenced by this script by Kepler Gelotte:
Image Color Extract

Input parameters are path to the image, number of colors to extract and delta - the amount of gap when quantizing color values (1-255). The smaller the delta, the more accurate the color, but also - number of similar colors increases.

<?phpdeclare(strict_types=1);namespace App\Service;class ColorPaletteExtractor{    private const PREVIEW_WIDTH = 250;    private const PREVIEW_HEIGHT = 250;    public function extractMainImgColors(string $imagePath, int $colorsCount, int $delta): array    {        $halfDelta = 0;        if ($delta > 2) {            $halfDelta = $delta / 2 - 1;        }        $size = getimagesize($imagePath);        $scale = 1;        if ($size[0] > 0) {            $scale = min(self::PREVIEW_WIDTH / $size[0], self::PREVIEW_HEIGHT / $size[1]);        }        $width = (int) $size[0];        $height = (int) $size[1];        if ($scale < 1) {            $width = (int) floor($scale * $size[0]);            $height = (int) floor($scale * $size[1]);        }        $imageResized = imagecreatetruecolor($width, $height);        $imageType = $size[2];        $imageOriginal = null;        if (IMG_JPEG === $imageType) {            $imageOriginal = imagecreatefromjpeg($imagePath);        }        if (IMG_GIF === $imageType) {            $imageOriginal = imagecreatefromgif($imagePath);        }        if (IMG_PNG === $imageType) {            $imageOriginal = imagecreatefrompng($imagePath);        }        imagecopyresampled($imageResized, $imageOriginal, 0, 0, 0, 0, $width, $height, $size[0], $size[1]);        $img = $imageResized;        $imgWidth = imagesx($img);        $imgHeight = imagesy($img);        $totalPixelCount = 0;        $hexArray = [];        for ($y = 0; $y < $imgHeight; $y++) {            for ($x = 0; $x < $imgWidth; $x++) {                $totalPixelCount++;                $index = imagecolorat($img, $x, $y);                $colors = imagecolorsforindex($img, $index);                if ($delta > 1) {                    $colors['red'] = intval((($colors['red']) + $halfDelta) / $delta) * $delta;                    $colors['green'] = intval((($colors['green']) + $halfDelta) / $delta) * $delta;                    $colors['blue'] = intval((($colors['blue']) + $halfDelta) / $delta) * $delta;                    if ($colors['red'] >= 256) {                        $colors['red'] = 255;                    }                    if ($colors['green'] >= 256) {                        $colors['green'] = 255;                    }                    if ($colors['blue'] >= 256) {                        $colors['blue'] = 255;                    }                }                $hex = substr('0' . dechex($colors['red']), -2)                    . substr('0' . dechex($colors['green']), -2)                    . substr('0' . dechex($colors['blue']), -2);                if (!isset($hexArray[$hex])) {                    $hexArray[$hex] = 0;                }                $hexArray[$hex]++;            }        }        // Reduce gradient colors        arsort($hexArray, SORT_NUMERIC);        $gradients = [];        foreach ($hexArray as $hex => $num) {            if (!isset($gradients[$hex])) {                $newHexValue = $this->findAdjacent((string) $hex, $gradients, $delta);                $gradients[$hex] = $newHexValue;            } else {                $newHexValue = $gradients[$hex];            }            if ($hex != $newHexValue) {                $hexArray[$hex] = 0;                $hexArray[$newHexValue] += $num;            }        }        // Reduce brightness variations        arsort($hexArray, SORT_NUMERIC);        $brightness = [];        foreach ($hexArray as $hex => $num) {            if (!isset($brightness[$hex])) {                $newHexValue = $this->normalize((string) $hex, $brightness, $delta);                $brightness[$hex] = $newHexValue;            } else {                $newHexValue = $brightness[$hex];            }            if ($hex != $newHexValue) {                $hexArray[$hex] = 0;                $hexArray[$newHexValue] += $num;            }        }        arsort($hexArray, SORT_NUMERIC);        // convert counts to percentages        foreach ($hexArray as $key => $value) {            $hexArray[$key] = (float) $value / $totalPixelCount;        }        if ($colorsCount > 0) {            return array_slice($hexArray, 0, $colorsCount, true);        } else {            return $hexArray;        }    }    private function normalize(string $hex, array $hexArray, int $delta): string    {        $lowest = 255;        $highest = 0;        $colors['red'] = hexdec(substr($hex, 0, 2));        $colors['green'] = hexdec(substr($hex, 2, 2));        $colors['blue'] = hexdec(substr($hex, 4, 2));        if ($colors['red'] < $lowest) {            $lowest = $colors['red'];        }        if ($colors['green'] < $lowest) {            $lowest = $colors['green'];        }        if ($colors['blue'] < $lowest) {            $lowest = $colors['blue'];        }        if ($colors['red'] > $highest) {            $highest = $colors['red'];        }        if ($colors['green'] > $highest) {            $highest = $colors['green'];        }        if ($colors['blue'] > $highest) {            $highest = $colors['blue'];        }        // Do not normalize white, black, or shades of grey unless low delta        if ($lowest == $highest) {            if ($delta > 32) {                return $hex;            }            if ($lowest == 0 || $highest >= (255 - $delta)) {                return $hex;            }        }        for (; $highest < 256; $lowest += $delta, $highest += $delta) {            $newHexValue = substr('0' . dechex($colors['red'] - $lowest), -2)                . substr('0' . dechex($colors['green'] - $lowest), -2)                . substr('0' . dechex($colors['blue'] - $lowest), -2);            if (isset($hexArray[$newHexValue])) {                // same color, different brightness - use it instead                return $newHexValue;            }        }        return $hex;    }    private function findAdjacent(string $hex, array $gradients, int $delta)    {        $red = hexdec(substr($hex, 0, 2));        $green = hexdec(substr($hex, 2, 2));        $blue = hexdec(substr($hex, 4, 2));        if ($red > $delta) {            $newHexValue = substr('0' . dechex($red - $delta), -2)                . substr('0' . dechex($green), -2)                . substr('0' . dechex($blue), -2);            if (isset($gradients[$newHexValue])) {                return $gradients[$newHexValue];            }        }        if ($green > $delta) {            $newHexValue = substr('0' . dechex($red), -2)                . substr('0' . dechex($green - $delta), -2)                . substr('0' . dechex($blue), -2);            if (isset($gradients[$newHexValue])) {                return $gradients[$newHexValue];            }        }        if ($blue > $delta) {            $newHexValue = substr('0' . dechex($red), -2)                . substr('0' . dechex($green), -2)                . substr('0' . dechex($blue - $delta), -2);            if (isset($gradients[$newHexValue])) {                return $gradients[$newHexValue];            }        }        if ($red < (255 - $delta)) {            $newHexValue = substr('0' . dechex($red + $delta), -2)                . substr('0' . dechex($green), -2)                . substr('0' . dechex($blue), -2);            if (isset($gradients[$newHexValue])) {                return $gradients[$newHexValue];            }        }        if ($green < (255 - $delta)) {            $newHexValue = substr('0' . dechex($red), -2)                . substr('0' . dechex($green + $delta), -2)                . substr('0' . dechex($blue), -2);            if (isset($gradients[$newHexValue])) {                return $gradients[$newHexValue];            }        }        if ($blue < (255 - $delta)) {            $newHexValue = substr('0' . dechex($red), -2)                . substr('0' . dechex($green), -2)                . substr('0' . dechex($blue + $delta), -2);            if (isset($gradients[$newHexValue])) {                return $gradients[$newHexValue];            }        }        return $hex;    }}

I was getting the best results when working with 6-10 colors and delta between 20 and 35.

CSS is pretty simple:

.gradient-background-animation {  background-size: 400% 400% !important;  animation: BackgroundGradient 10s ease infinite;}.card-picture-container {  padding: 0.875rem; // This value determines how big the "frame" will be}.card-picture-container img {  object-fit: contain;  max-width: 100%;}@keyframes BackgroundGradient {  0% {    background-position: 0 50%;  }  50% {    background-position: 100% 50%;  }  100% {    background-position: 0 50%;  }}

You can adjust the layout and the rest of the styling per your liking.

HTML:

<div class="container">  <div class="card-picture-container gradient-background-animation"        style="background-image: linear-gradient(-45deg, #78785a,#5a783c,#3c5a1e,#969678,#b4b4b4,#1e1e1e,#d2d2f0)">    <picture>      <img src="/images/source/image.jpeg" alt="">    </picture>  </div></div>

As you can notice, background is added as inline CSS.

Final result (size decreased for preview purposes):
Grid layout

I've created a Twig extension (filter) and included it in my Symfony project.

<?phpdeclare(strict_types=1);namespace App\Twig;use App\Service\ColorPaletteExtractor;use Twig\Extension\AbstractExtension;use Twig\TwigFilter;class GradientExtractorExtension extends AbstractExtension{    public function __construct(        private ColorPaletteExtractor $gradientExtractor,    ) {    }    public function getFilters(): array    {        return [            new TwigFilter('get_gradient', [$this, 'getGradient']),        ];    }    public function getGradient(string $imagePath, int $colorsCount, int $delta): string    {        $colors = $this->gradientExtractor->extractMainImgColors($imagePath, $colorsCount, $delta);        $filteredColors = array_filter($colors, fn (float $percentage) => $percentage > 0);        return join(',', array_map(fn (string $color) => '#' . $color, array_keys($filteredColors)));    }}

image-grid.html.twig

<div class="container">    {% for image in images %}        <div class="card-picture-container gradient-background-animation"             style="background-image: linear-gradient(-45deg, {{ asset(image)|get_gradient(7,30) }}); animation-duration: 8s;">            <picture>                <img src="{{ asset(image) }}" alt="">            </picture>        </div>    {% endfor %}</div>

Photos for testing purposes downloaded from https://www.parkovihrvatske.hr/parkovi


Original Link: https://dev.to/bornfightcompany/animate-a-frame-by-using-a-transition-between-main-image-colors-1p2j

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To