blog@timonwimmer

All over the place

Shell script for bulk-converting images to the webp format

April 27, 2023

While working on a project I needed a quick and customizable way of converting a lot of JPEG images to the much better webp format. Additionally, the tool also needed to resize the image if it is above a certain size (while still maintaining the aspect ratio if not 1:1), compress the image to a given quality and finally set a background color if the source is transparent. At first, I started to look for an existing tool – in the end I just came up with a quick shell script. Exercise is important!

#!/bin/bash

# Usage: x2webp.sh png 1000x1000 75 transparent

if [ -z "$1" ]; then
  echo "Please provide the file type flag (e.g. avif, jpg, jpeg, png, etc.)"
  exit 1
fi

if [ -z "$2" ]; then
  echo "Please provide the dimensions to rescale to (e.g. 1000 - only the width will be changed, the aspect ratio will be preserved)"
  exit 1
fi

if [ -z "$3" ]; then
  echo "Please provide the quality of the compression (e.g. 65 - retains 65% quality of the original)"
  exit 1
fi

if [ -z "$4" ]; then
  echo "Please provide a background color or just pass 'transparent' as the value"
  exit 1
fi

TYPE="$1"
SIZE="$2"
QUALITY="$3"
BG="$4"

# Convert all images with the specified image type to webp using ImageMagick
for file in *."$TYPE"; do
    # magick "$file" "${file%.*}.png"
    convert "$file" -background "$BG" -flatten "${file%.*}.webp";
done

# Downscale images or do nothing if original dimensions are larger than specified (again using ImageMagick)
for file in *.webp; do
    if [ $(identify -format '%w' "$file") -gt "${SIZE%x*}" ] || [ $(identify -format '%h' "$file") -gt "${SIZE#*x}" ]; then
        convert "$file" -resize "$SIZE"\> "$file"
    fi
done

# Compress images while still retaining acceptable quality (65%~) using cwebp
for file in *.webp; do
    cwebp -q "$QUALITY" "$file" -o "${file%.*}_compressed.webp"
done

As always, do be able to run this shell script in the console, you need to grant the current user execution permissions like below (assuming you named the script “x2webp.sh”)

chmod u+x x2webp.sh

GROUP BY clause and nonaggregated columns

January 26, 2023

If STRICT_TRANS_TABLES is set in the sql_mode variable, the MySQL server is running in strict mode. Either disabling strict mode or writing the statement with ANY_VALUE() will fix this error.

Fatal error: Uncaught PDOException: SQLSTATE[42000]: Syntax error or access violation: 1055 Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'table.xyz' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by in /var/www/examplecom/xyz.php:32
SELECT ANY_VALUE(table.x) as x, ANY_VALUE(table.y) as y
	GROUP BY x
	ORDER BY y DESC

Object-Oriented_Colors.php

January 25, 2023

<?php

// Copyright (c) 2023 Timon Wimmer-Cuport
// This code is licensed under MIT license (see LICENSE.txt for details)

class Colors {
	private $H;
	private $S;
	private $L;
	// H = hue, S = saturation, L = lightness

	public $darkest;
	public $primary;
	public $primaryLight;
	public $primaryDark;
	public $complementary;

	function __construct($h = false, $s = false, $l = false) {
		if (!$h) $this->H = ($this->random() * 360);
		else $this->H = $h;
		if (!$s) $this->S = 40;
		else $this->S = $s;
		if (!$l) $this->L = 40;
		else $this->L = $l;
		$this->setColors();
	}

	function random() {
		return (float)rand()/(float)getrandmax();
	}

	function fHSL($n = false, $h, $s, $l, $rgb = true) {
		if ($n === false) return false;
		$s /= 100;
		$l /= 100;
		$a = $s * min($l, 1 - $l);
		$k = (($n + ($h / 30)) % 12);
		$x = $l - $a * max(min($k - 3, 9 - $k, 1), -1);
		if ($rgb) $x *= 255;
		return $x;
	}

	function HSLtoRGB($h = false, $s = false, $l = false) {
		if (!$h) return false;
		if (!$s) return false;
		if (!$l) return false;
		return [$this->fHSL(0, $h, $s, $l), $this->fHSL(8, $h, $s, $l), $this->fHSL(4, $h, $s, $l)];
	}

	function RGBtoHex($r, $g, $b) {
		$hex = sprintf("#%02x%02x%02x", $r, $g, $b);
		return $hex;
	}

	function contrastYIQ($rgb, $reverse = false) {
		$yiq = (($rgb[0] * 299) + ($rgb[1] * 587) + ($rgb[2] * 114)) / 1000;
		if (!$reverse) return ($yiq >= 128) ? "#000000" : "#ffffff";
		else return ($yiq >= 128) ? "#ffffff" : "#000000";
		
	}

	function hueToRGB($p, $q, $t) {
		if ($t < 0) $t += 1;
		if ($t > 1) $t -= 1;
		if ($t < 1/6) return $p + ($q - $p) * 6 * $t;
		if ($t < 1/2) return $q;
		if ($t < 2/3) return $p + ($q - $p) * (2/3 - $t) * 6;
		return $p;
	}

	function getComplementary($rgb) {
		$r = $rgb[0];
		$g = $rgb[1];
		$b = $rgb[2];
		$r /= 255.0;
		$g /= 255.0;
		$b /= 255.0;
		$max = max($r, $g, $b);
		$min = min($r, $g, $b);
		$h = ($max + $min) / 2.0;
		$s = ($max + $min) / 2.0;
		$l = ($max + $min) / 2.0;
		if ($max == $min) {
			$h = 0;
			$s = 0;
		}
		else {
			$d = $max - $min;
			$s = ($l > 0.5 ? $d / (2.0 - $max - $min) : $d / ($max + $min));
			if ($max == $r && $g >= $b) $h = 1.0472 * ($g - $b) / $d;
			else if ($max == $r && $g < $b) $h = 1.0472 * ($g - $b) / $d + 6.2832;
			else if ($max == $g) $h = 1.0472 * ($b - $r) / $d + 2.0944;
			else if ($max == $b) $h = 1.0472 * ($r - $g) / $d + 4.1888;
		}
		$h = $h / 6.2832 * 360.0 + 0;
		$h += 180;
		if ($h > 360) $h -= 360;
		$h /= 360;
		if ($s === 0) {
			$r = $g = $b = $l;
		}
		else {
			$q = $l < 0.5 ? $l * (1 + $s) : $l + $s - $l * $s;
			$p = 2 * $l - $q;
			$r = $this->hueToRGB($p, $q, $h + 1/3);
			$g = $this->hueToRGB($p, $q, $h);
			$b = $this->hueToRGB($p, $q, $h - 1/3);
		}
		$r = round($r * 255);
		$b = round($b * 255);
		$g = round($g * 255);
		return [$r, $g, $b];
	}

	function setColors() {
		$rgb = $this->HSLtoRGB($this->H, $this->S, $this->L);
		$this->primary = $rgb;
		$this->primaryText = $this->contrastYIQ($rgb);
		$this->primaryLight = $this->HSLtoRGB($this->H, $this->S, 85);
		$this->primaryDark = $this->HSLtoRGB($this->H, $this->S, 25);
		$this->darkest = $this->HSLtoRGB($this->H, 80, 6);
		$rgbComplementary = $this->HSLtoRGB($this->H, 55, 70);
		$this->complementary = $this->getComplementary($rgbComplementary);
	}

	function getColor($rgb, $alpha = false, $toHex = false) {
		if (!$rgb) return false;
		$rgbStr = "rgb(";
		if ($alpha) $rgbStr = "rgba(";
		$rgbStr .= $rgb[0] . ", " . $rgb[1] . ", " . $rgb[2];
		if ($alpha) $rgbStr .= ", " . $alpha;
		$rgbStr .= ")";
		return $rgbStr;
	}

}

$colors = new Colors(/* $h, $s, $l */);

echo 'Darkest: ' . $colors->getColor($colors->darkest);
echo 'Primary: ' . $colors->getColor($colors->primary);
echo 'Primary Light: ' . $colors->getColor($colors->primaryLight);
echo 'Primary Dark: ' . $colors->getColor($colors->primaryDark);
echo 'Secondary (Complementary): ' . $colors->getColor($colors->complementary);
Darkest: rgb(27.54, 3.06, 15.3)
Primary: rgb(142.8, 61.2, 102)
Check for inverse text color...
Primary Light: rgb(232.05, 201.45, 216.75)
Primary Dark: rgb(89.25, 38.25, 63.75)
Secondary (Complementary): rgb(136, 221, 179)