From 2959f11f339ece467db1a22da41b2cf7f119806f Mon Sep 17 00:00:00 2001
From: Mikael Roos <mikael.t.h.roos@gmail.com>
Date: Tue, 1 Sep 2015 16:45:10 +0200
Subject: [PATCH] Adding support for AsciiArt of an image.

---
 CAsciiArt.php          | 213 +++++++++++++++++++++++++++++++++++++++++
 CImage.php             |  43 +++++++++
 webroot/img.php        |  48 +++++++++-
 webroot/img_config.php |  21 ++++
 4 files changed, 323 insertions(+), 2 deletions(-)
 create mode 100644 CAsciiArt.php

diff --git a/CAsciiArt.php b/CAsciiArt.php
new file mode 100644
index 0000000..ac3535b
--- /dev/null
+++ b/CAsciiArt.php
@@ -0,0 +1,213 @@
+<?php
+/**
+ * Create an ASCII version of an image.
+ * Inspired by https://gist.github.com/donatj/1353237 and various sources.
+ *
+ */
+class CAsciiArt
+{
+    /**
+     * Character set to use.
+     */
+    private $characterSet = array(
+        'one' => "#0XT|:,.' ",
+        'two' => "@%#*+=-:. ",
+        'three' => "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. "
+    );
+
+
+
+    /**
+     * Current character set.
+     */
+    private $characters = null;
+
+
+
+    /**
+     * Length of current character set.
+     */
+    private $charCount = null;
+
+
+
+    /**
+     * Scale of the area to swap to a character.
+     */
+    private $scale = null;
+
+
+
+    /**
+     * Strategy to calculate luminance.
+     */
+    private $luminanceStrategy = null;
+
+
+
+    /**
+     * Constructor which sets default options.
+     */
+    public function __construct()
+    {
+        $this->setOptions();
+    }
+
+
+
+    /**
+     * Add a custom character set.
+     *
+     * @param string $key   for the character set.
+     * @param string $value for the character set.
+     *
+     * @return $this
+     */
+    public function addCharacterSet($key, $value)
+    {
+        $this->characterSet[$key] = $value;
+        return $this;
+    }
+
+
+
+    /**
+     * Length of current character set.
+     *
+     * @param array $options to use as default settings.
+     *
+     * @return $this
+     */
+    public function setOptions($options = array())
+    {
+        $default = array(
+            "characterSet" => 'two',
+            "scale" => 14,
+            "luminanceStrategy" => 3,
+            "customCharacterSet" => null,
+        );
+        $default = array_merge($default, $options);
+        
+        if (!is_null($default['customCharacterSet'])) {
+            $this->addCharacterSet('custom', $default['customCharacterSet']);
+            $default['characterSet'] = 'custom';
+        }
+        
+        $this->scale = $default['scale'];
+        $this->characters = $this->characterSet[$default['characterSet']];
+        $this->charCount = strlen($this->characters);
+        $this->luminanceStrategy = $default['luminanceStrategy'];
+        
+        return $this;
+    }
+
+
+
+    /**
+     * Create an Ascii image from an image file.
+     *
+     * @param string $filename of the image to use.
+     *
+     * @return string $ascii with the ASCII image.
+     */
+    public function createFromFile($filename)
+    {
+        $img = imagecreatefromstring(file_get_contents($filename));
+        list($width, $height) = getimagesize($filename);
+
+        $ascii = null;
+        $incY = $this->scale;
+        $incX = $this->scale / 2;
+        
+        for ($y = 0; $y < $height - 1; $y += $incY) {
+            for ($x = 0; $x < $width - 1; $x += $incX) {
+                $toX = min($x + $this->scale / 2, $width - 1);
+                $toY = min($y + $this->scale, $height - 1);
+                $luminance = $this->luminanceAreaAverage($img, $x, $y, $toX, $toY);
+                $ascii .= $this->luminance2character($luminance);
+            }
+            $ascii .= PHP_EOL;
+        }
+
+        return $ascii;
+    }
+
+
+
+    /**
+     * Get the luminance from a region of an image using average color value.
+     *
+     * @param string  $img the image.
+     * @param integer $x1  the area to get pixels from.
+     * @param integer $y1  the area to get pixels from.
+     * @param integer $x2  the area to get pixels from.
+     * @param integer $y2  the area to get pixels from.
+     *
+     * @return integer $luminance with a value between 0 and 100.
+     */
+    public function luminanceAreaAverage($img, $x1, $y1, $x2, $y2)
+    {
+        $numPixels = ($x2 - $x1 + 1) * ($y2 - $y1 + 1);
+        $luminance = 0;
+        
+        for ($x = $x1; $x <= $x2; $x++) {
+            for ($y = $y1; $y <= $y2; $y++) {
+                $rgb   = imagecolorat($img, $x, $y);
+                $red   = (($rgb >> 16) & 0xFF);
+                $green = (($rgb >> 8) & 0xFF);
+                $blue  = ($rgb & 0xFF);
+                $luminance += $this->getLuminance($red, $green, $blue);
+            }
+        }
+        
+        return $luminance / $numPixels;
+    }
+
+
+
+    /**
+     * Calculate luminance value with different strategies.
+     *
+     * @param integer $red   The color red.
+     * @param integer $green The color green.
+     * @param integer $blue  The color blue.
+     *
+     * @return float $luminance with a value between 0 and 1.
+     */
+    public function getLuminance($red, $green, $blue)
+    {
+        switch($this->luminanceStrategy) {
+            case 1:
+                $luminance = ($red * 0.2126 + $green * 0.7152 + $blue * 0.0722) / 255;
+                break;
+            case 2:
+                $luminance = ($red * 0.299 + $green * 0.587 + $blue * 0.114) / 255;
+                break;
+            case 3:
+                $luminance = sqrt(0.299 * pow($red, 2) + 0.587 * pow($green, 2) + 0.114 * pow($blue, 2)) / 255;
+                break;
+            case 0:
+            default:
+                $luminance = ($red + $green + $blue) / (255 * 3);
+        }
+
+        return $luminance;
+    }
+
+
+
+    /**
+     * Translate the luminance value to a character.
+     *
+     * @param string $position a value between 0-100 representing the
+     *                         luminance.
+     *
+     * @return string with the ascii character.
+     */
+    public function luminance2character($luminance)
+    {
+        $position = (int) round($luminance * ($this->charCount - 1));
+        $char = $this->characters[$position];
+        return $char;
+    }
+}
diff --git a/CImage.php b/CImage.php
index 24be590..80bd97a 100644
--- a/CImage.php
+++ b/CImage.php
@@ -334,6 +334,13 @@ class CImage
 
 
 
+    /*
+     * output to ascii can take som options as an array.
+     */
+    private $asciiOptions = array();
+
+
+
     /**
      * Properties, the class is mutable and the method setOptions()
      * decides (partly) what properties are created.
@@ -2250,6 +2257,10 @@ class CImage
             header('Content-type: application/json');
             echo $this->json($file);
             exit;
+        } elseif ($format == 'ascii') {
+            header('Content-type: text/plain');
+            echo $this->ascii($file);
+            exit;
         }
 
         $this->log("Outputting image: $file");
@@ -2341,6 +2352,38 @@ class CImage
 
 
 
+    /**
+     * Set options for creating ascii version of image.
+     *
+     * @param array $options empty to use default or set options to change.
+     *
+     * @return void.
+     */
+    public function setAsciiOptions($options = array())
+    {
+        $this->asciiOptions = $options;
+    }
+
+
+
+    /**
+     * Create an ASCII version from the image details.
+     *
+     * @param string $file the file to output.
+     *
+     * @return string ASCII representation of the image.
+     */
+    public function ascii($file = null)
+    {
+        $file = $file ? $file : $this->cacheFileName;
+
+        $asciiArt = new CAsciiArt();
+        $asciiArt->setOptions($this->asciiOptions);
+        return $asciiArt->createFromFile($file);
+    }
+
+
+
     /**
      * Log an event if verbose mode.
      *
diff --git a/webroot/img.php b/webroot/img.php
index 350c0d8..7efe537 100644
--- a/webroot/img.php
+++ b/webroot/img.php
@@ -754,11 +754,55 @@ verbose("filters = " . print_r($filters, 1));
 
 
 /**
- * json - output the image as a JSON object with details on the image.
+* json -  output the image as a JSON object with details on the image.
+* ascii - output the image as ASCII art.
  */
 $outputFormat = getDefined('json', 'json', null);
+$outputFormat = getDefined('ascii', 'ascii', $outputFormat);
+
+verbose("outputformat = $outputFormat");
+
+if ($outputFormat == 'ascii') {
+    $defaultOptions = getConfig(
+        'ascii-options',
+        array(
+            "characterSet" => 'two',
+            "scale" => 14,
+            "luminanceStrategy" => 3,
+            "customCharacterSet" => null,
+        )
+    );
+    $options = get('ascii');
+    $options = explode(',', $options);
+
+    if (isset($options[0]) && !empty($options[0])) {
+        $defaultOptions['characterSet'] = $options[0];
+    }
+
+    if (isset($options[1]) && !empty($options[1])) {
+        $defaultOptions['scale'] = $options[1];
+    }
+
+    if (isset($options[2]) && !empty($options[2])) {
+        $defaultOptions['luminanceStrategy'] = $options[2];
+    }
+
+    if (count($options) > 3) {
+        // Last option is custom character string
+        unset($options[0]);
+        unset($options[1]);
+        unset($options[2]);
+        $characterString = implode($options);
+        $defaultOptions['customCharacterSet'] = $characterString;
+    }
+
+    //var_dump($options);
+    //var_dump($defaultOptions);
+    //exit;
+
+    $img->setAsciiOptions($defaultOptions);
+}
 
-verbose("json = $outputFormat");
 
 
 
diff --git a/webroot/img_config.php b/webroot/img_config.php
index d071cac..64223a2 100644
--- a/webroot/img_config.php
+++ b/webroot/img_config.php
@@ -299,4 +299,25 @@ return array(
             'golden' => 1.618,
         );
     },*/
+
+
+
+    /**
+     * default options for ascii image.
+     *
+     * Default values as specified below in the array.
+     *  ascii-options:
+     *   characterSet:       Choose any character set available in CAsciiArt.
+     *   scale:              How many pixels should each character
+     *                       translate to.
+     *   luminanceStrategy:  Choose any strategy available in CAsciiArt.
+     *   customCharacterSet: Define your own character set.
+     */
+    /*'ascii-options' => array(
+            "characterSet" => 'two',
+            "scale" => 14,
+            "luminanceStrategy" => 3,
+            "customCharacterSet" => null,
+        );
+    },*/
 );