-
-
Save imzhi/5f794987032edcd31c8affad660da28c to your computer and use it in GitHub Desktop.
Draw image in terminal (True Color) with PHP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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