Skip to content

Instantly share code, notes, and snippets.

@imzhi
Forked from robopuff/paint
Created October 9, 2024 09:09
Show Gist options
  • Select an option

  • Save imzhi/5f794987032edcd31c8affad660da28c to your computer and use it in GitHub Desktop.

Select an option

Save imzhi/5f794987032edcd31c8affad660da28c to your computer and use it in GitHub Desktop.
Draw image in terminal (True Color) with PHP
#!/usr/bin/env php
<?php
error_reporting(0);
define('VERSION', '1.0.0');
define('DEFAULT_PIXEL', '▄');
define('RC', "\e[0m");
define('RCNL', RC . PHP_EOL);
// --------------------------------------------------------
// Flags in use
$flags = [
'-b' => [
'value' => true,
'value_required' => false,
'description' => 'Resize to [VALUE] box, if no value provided then terminal height is used',
'callback' => function (&$width, &$height, $value = null) {
if ($value) {
$height = $value;
} else {
$height = ((int)exec('tput lines'));
}
$width = $height;
}
],
'-w' => [
'value' => true,
'value_required' => true,
'description' => 'Set width',
'callback' => function (&$width, $height, $value) {
$width = $value;
}
],
'-h' => [
'value' => true,
'value_required' => true,
'description' => 'Set height',
'callback' => function ($width, &$height, $value) {
$height = $value;
}
],
'-t' => [
'value' => false,
'value_required' => false,
'description' => 'Test terminal color & font capabilities. It should show two lines of different color.',
'callback' => function () {
echo drawChar([255,80,105], [105, 80, 255]) . str_repeat(drawLowerPixel([]), 20) . RCNL;
}
],
'-p' => [
'value' => false,
'value_required' => false,
'description' => 'Draw palette of colours, good for terminal test',
'callback' => function ($width, $height) {
$source = imagecreatetruecolor(255, 255);
for ($y = 0; $y < 255; $y++) {
for ($x = 0; $x < 255; $x++) {
$r = $x;
$g = $y;
$b = ($x + $y) / 2;
imagesetpixel($source, $x, $y, imagecolorallocate($source, $r, $g, $b));
}
}
imagefilter($source, IMG_FILTER_PIXELATE, 2, true);
drawImage($source, 255, 255, $width, $height);
}
],
'-f' => [
'value' => true,
'value_required' => true,
'description' => 'Draw image provided in [VALUE], can combine with `-b` but `-f` must be provided last.' .
' Can be skipped if <FILE> is provided.',
'callback' => function ($width, $height, $value) {
$content = null;
if (!$value) {
throw new \Exception('Improper usage of -f, should be -f=[IMAGE]');
}
if (strpos($value, 'http') === 0) {
$curl = curl_init($value);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, false);
$content = curl_exec($curl);
curl_close($curl);
} elseif (!file_exists($value) || !is_readable($value)) {
throw new \Exception("Cannot find file `{$value}`");
}
if (!$content) {
$content = file_get_contents($value);
}
$source = imagecreatefromstring($content);
if (!$source) {
throw new \Exception("Cannot read `{$value}` as image");
}
$imW = imagesx($source);
$imH = imagesy($source);
imagefilter($source, IMG_FILTER_PIXELATE, 2, true);
drawImage($source, $imW, $imH, $width, $height);
imagedestroy($source);
}
]
];
// --------------------------------------------------------
// Helper functions
// Draw single pixel with sub-pixel as lower block char
function drawChar(array $upper, array $lower = [], $char = DEFAULT_PIXEL, $clean = false) // •
{
if (empty($upper) && empty($lower)) {
return $char;
}
return drawUpperPixel($upper) .
drawLowerPixel(empty($lower) ? $upper : $lower, $char) .
($clean ? RC : "");
};
// Draw background color (upper pixel)
function drawUpperPixel(array $color)
{
$color = implode(';', $color);
return "\e[48;2;{$color}m";
}
// Draw foreground color (lower pixel)
function drawLowerPixel(array $color = [], $char = DEFAULT_PIXEL)
{
if (empty($color)) {
return $char;
}
$color = implode(';', $color);
return "\e[38;2;{$color}m{$char}";
}
// Draw image pixel by pixel (with resize)
function drawImage($source, $imW, $imH, $width, $height)
{
$r = $imW / $imH;
if ($height > 0) {
if ($width > $height) {
$height = $width / $r;
} else {
$width = $height * $r;
}
} else {
$height = $width / $r;
}
$thumb = imagecreatetruecolor($width, $height);
imagecopyresampled($thumb, $source, 0, 0, 0, 0, $width, $height, $imW, $imH);
for ($y = 0; $y < $height - 1; $y++) {
if ($y % 2 != 0) {
continue;
}
$lastUpper = [];
$lastLower = [];
for ($x = 0; $x < $width - 1; $x++) {
$c1 = imagecolorat($thumb, $x, $y);
$c2 = imagecolorat($thumb, $x, $y + 1);
$upper = [
($c1 >> 16) & 0xFF,
($c1 >> 8) & 0xFF,
($c1) & 0xFF,
];
$lower = $drawLower = [
($c2 >> 16) & 0xFF,
($c2 >> 8) & 0xFF,
($c2) & 0xFF,
];
if ($lastUpper != $upper) {
echo drawUpperPixel($upper);
}
if ($lastLower == $lower) {
$drawLower = [];
}
echo drawLowerPixel($drawLower);
$lastLower = $lower;
$lastUpper = $upper;
}
echo RCNL;
}
imagedestroy($thumb);
}
// --------------------------------------------------------
// Terminal operations
$termWidth = (int)exec('tput cols') - 1;
$termHeight = 0;
$currentFile = array_shift($argv);
$file = end($argv);
if ($file[0] != '-' && (file_exists($file) || strpos($file, 'http') === 0)) {
$argv[] = "-f={$file}";
}
if (count($argv) < 1) {
echo drawLowerPixel([200, 20, 70], '');
echo <<<ASCII
::::::::: ::: ::::::::::::::: ::::::::::::::
:+: :+: :+: :+: :+: :+:+: :+: :+:
+:+ +:++:+ +:+ +:+ :+:+:+ +:+ +:+
+#++:++#++#++:++#++: +#+ +#+ +:+ +#+ +#+
+#+ +#+ +#+ +#+ +#+ +#+#+# +#+
#+# #+# #+# #+# #+# #+#+# #+#
### ### ################# #### ### v
ASCII;
echo VERSION . RCNL . PHP_EOL;
$help = "Usage `{$currentFile}` [PARAMS] <FILE>\n";
foreach ($flags as $flag => $options) {
$flagHelp = $flag;
if ($options['value']) {
if ($options['value_required']) {
$flagHelp .= "=[VALUE]";
} else {
$flagHelp .= ", {$flag}=[VALUE]";
}
}
$help .= " {$flagHelp}\n {$options['description']}\n";
}
$help = preg_replace_callback('/\[(.*?)\]/', function ($matches) {
return drawChar([45, 35, 8], [255, 247, 97], $matches[0], true);
}, $help);
$help = preg_replace_callback('/\`(.*?)\`/', function ($matches) {
return drawChar([35, 13, 14], [255, 100, 108], " {$matches[1]} ", true);
}, $help);
$help = preg_replace_callback('/\<(.*?)\>/', function ($matches) {
return drawChar([10, 38, 13], [102, 245, 119], $matches[0], true);
}, $help);
echo "{$help}\n";
die();
}
foreach ($argv as $aid => $arg) {
$key = $arg;
$value = null;
$pos = strpos($arg, '=');
if ($pos > 0) {
$key = substr($arg, 0, $pos);
$value = substr($arg, $pos + 1);
}
if (isset($flags[$key])) {
$callback = $flags[$key]['callback'];
try {
$callback($termWidth, $termHeight, $value);
} catch (\Exception $e) {
$bg = [200, 20, 70];
echo drawChar($bg, [255,255,255], str_repeat(' ', 50)) . "\n";
echo ' Error occurred' . str_repeat(' ', 50 - 15) . "\n";
echo str_repeat(' ', 50) . RCNL . PHP_EOL;
echo ' ' . $e->getMessage() . "\n\n";
}
}
}
__halt_compiler();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment