├── .gitignore
├── .travis.yml
├── ChangeLog.md
├── LICENSE
├── README.md
├── composer.json
├── docs
└── images
│ ├── octocat.png
│ ├── sb.png
│ ├── sin.png
│ └── turtle.png
├── examples
├── ImagePrinter.php
├── basic.php
├── composer.json
├── img2term.php
├── octocat2term.php
├── turtle.php
└── xkcd2term.php
├── phpunit.xml.dist
├── src
└── Drawille
│ ├── Canvas.php
│ └── Turtle.php
└── tests
└── Drawille
├── CanvasTest.php
└── TurtleTest.php
/.gitignore:
--------------------------------------------------------------------------------
1 | composer.lock
2 | composer.phar
3 | phpunit.xml
4 | vendor
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.4
5 | - 5.5
6 | - 5.6
7 | - hhvm
8 |
9 | before_script:
10 | - composer install --prefer-source
11 |
12 | script: vendor/bin/phpunit
--------------------------------------------------------------------------------
/ChangeLog.md:
--------------------------------------------------------------------------------
1 | ChangeLog
2 | =========
3 |
4 | 1.0.1
5 | -----
6 |
7 | * Fixed notice.
8 | * Added more examples.
9 |
10 | 1.0.0
11 | -----
12 |
13 | * It begins!
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Jeff Welch
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is furnished
8 | to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | php-drawille
2 | ============
3 |
4 | Terminal drawing with [braille](http://en.wikipedia.org/wiki/Braille).
5 |
6 | 
7 |
8 | ## Requirements
9 |
10 | php-drawille requires PHP 5.4.0 or later.
11 |
12 | ## Installation
13 |
14 | The recommended way to install php-drawille is [through
15 | composer](http://getcomposer.org). Just create a `composer.json` file and
16 | run the `php composer.phar install` command to install it:
17 |
18 | ~~~json
19 | {
20 | "require": {
21 | "whatthejeff/drawille": "~1.0"
22 | }
23 | }
24 | ~~~
25 |
26 | ## Usage
27 |
28 | ~~~php
29 | use Drawille\Canvas;
30 |
31 | $canvas = new Canvas();
32 |
33 | for($x = 0; $x <= 1800; $x += 10) {
34 | $canvas->set($x / 10, 10 + sin($x * M_PI / 180) * 10);
35 | }
36 |
37 | echo $canvas->frame(), "\n";
38 | ~~~
39 |
40 | 
41 |
42 | ~~~php
43 | use Drawille\Turtle;
44 |
45 | $turtle = new Turtle();
46 |
47 | for($x = 0; $x < 36; $x++) {
48 | $turtle->right(10);
49 |
50 | for($y = 0; $y < 36; $y++) {
51 | $turtle->right(10);
52 | $turtle->forward(8);
53 | }
54 | }
55 |
56 | echo $turtle->frame(), "\n";
57 | ~~~
58 |
59 | 
60 |
61 | ## Examples
62 |
63 | To use the scripts in the [examples](examples) directory, you need to install the
64 | dependencies with composer.
65 |
66 | $ cd examples
67 | $ php composer.phar install
68 | $ ./img2term.php --fab --threshold 600 ~/Pictures/sb.png
69 |
70 | 
71 |
72 | ## Tests
73 |
74 | [](https://travis-ci.org/whatthejeff/php-drawille)
75 |
76 | To run the test suite, you need [composer](http://getcomposer.org).
77 |
78 | $ php composer.phar install
79 | $ vendor/bin/phpunit
80 |
81 | ## Acknowledgements
82 |
83 | php-drawille is a port of [drawille](https://github.com/asciimoo/drawille).
84 |
85 | ## License
86 |
87 | php-drawille is licensed under the [MIT license](LICENSE).
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "whatthejeff/drawille",
3 | "description": "Terminal drawing with braille",
4 | "keywords": ["terminal","console","braille","drawing","turtle"],
5 | "homepage": "http://github.com/whatthejeff/php-drawille",
6 | "license": "MIT",
7 | "authors": [
8 | {
9 | "name": "Jeff Welch",
10 | "email": "whatthejeff@gmail.com"
11 | }
12 | ],
13 | "require": {
14 | "php": ">=5.4.0"
15 | },
16 | "require-dev": {
17 | "phpunit/phpunit": "4.1.*"
18 | },
19 | "autoload": {
20 | "psr-0": {
21 | "Drawille": "src/"
22 | }
23 | },
24 | "extra": {
25 | "branch-alias": {
26 | "dev-master": "1.0.x-dev"
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/docs/images/octocat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whatthejeff/php-drawille/541b2517a70b988d9f2a6249603f7331c1814957/docs/images/octocat.png
--------------------------------------------------------------------------------
/docs/images/sb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whatthejeff/php-drawille/541b2517a70b988d9f2a6249603f7331c1814957/docs/images/sb.png
--------------------------------------------------------------------------------
/docs/images/sin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whatthejeff/php-drawille/541b2517a70b988d9f2a6249603f7331c1814957/docs/images/sin.png
--------------------------------------------------------------------------------
/docs/images/turtle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whatthejeff/php-drawille/541b2517a70b988d9f2a6249603f7331c1814957/docs/images/turtle.png
--------------------------------------------------------------------------------
/examples/ImagePrinter.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | use Imagine\Gd\Imagine;
13 | use Imagine\Image\Box;
14 | use Imagine\Image\Point;
15 |
16 | use Drawille\Canvas;
17 |
18 | use Fab\SuperFab;
19 |
20 | class ImagePrinter
21 | {
22 | private $image;
23 | private $threshold;
24 | private $ratio;
25 | private $invert;
26 | private $fab;
27 |
28 | public function __construct($image, $threshold = 385.2, $ratio = null, $invert = false, $fab = false) {
29 | $this->image = $image;
30 | $this->threshold = (float) $threshold;
31 | $this->ratio = $ratio;
32 | $this->invert = $invert;
33 | $this->fab = $fab;
34 | }
35 |
36 | public function run($terminalWidth, $terminalHeight) {
37 | $imagine = new Imagine();
38 | $image = $imagine->open($this->image);
39 |
40 | $size = $image->getSize();
41 | $width = $size->getWidth();
42 | $height = $size->getHeight();
43 |
44 | if ($this->ratio) {
45 | $ratio = (float) $this->ratio;
46 | $width = floor($width * $ratio);
47 | $height = floor($height * $ratio);
48 | $image->resize(new Box($width, $height));
49 | }
50 |
51 | else {
52 | $height_ratio = $terminalHeight * 4 / $height;
53 | $width_ratio = $terminalWidth * 2 / $width;
54 | $ratio = min($height_ratio, $width_ratio);
55 |
56 | if ($ratio < 1.0) {
57 | $width = floor($width * $ratio);
58 | $height = floor($height * $ratio);
59 | $image->resize(new Box($width, $height));
60 | }
61 | }
62 |
63 | $canvas = new Canvas();
64 |
65 | for ($y = 0; $y < $height; $y++) {
66 | for ($x = 0; $x < $width; $x++) {
67 | $color = $image->getColorAt(new Point($x, $y));
68 | $total = $color->getRed() + $color->getGreen() + $color->getBlue();
69 |
70 | if (!$this->invert ^ $total > $this->threshold) {
71 | $canvas->set($x, $y);
72 | }
73 | }
74 | }
75 |
76 | if ($this->fab) {
77 | $fab = new SuperFab();
78 | echo $fab->paint($canvas->frame()), "\n";
79 | }
80 |
81 | else {
82 | echo $canvas->frame(), "\n";
83 | }
84 | }
85 | }
86 |
87 | ?>
--------------------------------------------------------------------------------
/examples/basic.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | require_once __DIR__ . '/vendor/autoload.php';
13 |
14 | use Drawille\Canvas;
15 |
16 | $canvas = new Canvas();
17 |
18 | for ($x = 0; $x <= 1800; $x++) {
19 | $canvas->set($x / 10, sin($x * M_PI / 180) * 10);
20 | }
21 |
22 | echo $canvas->frame(), "\n";
23 | $canvas->clear();
24 |
25 | for ($x = 0; $x <= 1800; $x += 10) {
26 | $canvas->set($x / 10, 10 + sin($x * M_PI / 180) * 10);
27 | $canvas->set($x / 10, 10 + cos($x * M_PI / 180) * 10);
28 | }
29 |
30 | echo $canvas->frame(), "\n";
31 | $canvas->clear();
32 |
33 | for ($x = 0; $x <= 3600; $x += 20) {
34 | $canvas->set($x / 20, 4 + sin($x * M_PI / 180) * 4);
35 | }
36 |
37 | echo $canvas->frame(), "\n";
38 | $canvas->clear();
39 |
40 | for ($x = 0; $x <= 360; $x += 4) {
41 | $canvas->set($x / 4, 30 + sin($x * M_PI / 180) * 30);
42 | }
43 |
44 | for ($x = 0; $x <= 30; $x++) {
45 | for ($y = 0; $y <= 30; $y++) {
46 | $canvas->set($x, $y);
47 | $canvas->toggle($x+30, $y+30);
48 | $canvas->toggle($x+60, $y);
49 | }
50 | }
51 |
52 | echo $canvas->frame(), "\n";
--------------------------------------------------------------------------------
/examples/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "whatthejeff/drawille-examples",
3 | "description": "drawille examples",
4 | "keywords": ["terminal","console","braille","drawing","xkcd","octocat","image"],
5 | "homepage": "http://github.com/whatthejeff/drawille",
6 | "license": "MIT",
7 | "authors": [
8 | {
9 | "name": "Jeff Welch",
10 | "email": "whatthejeff@gmail.com"
11 | }
12 | ],
13 | "require": {
14 | "php": ">=5.4.0",
15 | "imagine/imagine": "~0.5.0",
16 | "symfony/console": "~2.4",
17 | "fabpot/Goutte": "~2.0",
18 | "whatthejeff/drawille": "~1.0",
19 | "whatthejeff/fab": "~1.0",
20 | "ext-gd": "*"
21 | },
22 | "autoload": {
23 | "psr-0": {
24 | "ImagePrinter": ""
25 | }
26 | },
27 | "extra": {
28 | "branch-alias": {
29 | "dev-master": "1.0.x-dev"
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/examples/img2term.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 |
8 | *
9 | * For the full copyright and license information, please view the LICENSE
10 | * file that was distributed with this source code.
11 | */
12 |
13 | require_once __DIR__ . '/vendor/autoload.php';
14 |
15 | use Symfony\Component\Console\Application as ConsoleApplication;
16 | use Symfony\Component\Console\Command\Command as ConsoleCommand;
17 |
18 | use Symfony\Component\Console\Input\InputArgument;
19 | use Symfony\Component\Console\Input\InputOption;
20 | use Symfony\Component\Console\Input\InputDefinition;
21 | use Symfony\Component\Console\Input\InputInterface;
22 | use Symfony\Component\Console\Output\OutputInterface;
23 |
24 | class Application extends ConsoleApplication
25 | {
26 | public function __construct() {
27 | parent::__construct(basename(__FILE__), '1.0');
28 | $this->add(new Command);
29 | }
30 |
31 | protected function getCommandName(InputInterface $input) {
32 | return basename(__FILE__);
33 | }
34 |
35 | protected function getDefaultInputDefinition()
36 | {
37 | return new InputDefinition(array(
38 | new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message.'),
39 | new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version.')
40 | ));
41 | }
42 | }
43 |
44 | class Command extends ConsoleCommand
45 | {
46 | protected function configure()
47 | {
48 | $this->setName(basename(__FILE__))
49 | ->setDescription('convert an image to terminal')
50 | ->addArgument(
51 | 'image',
52 | InputArgument::REQUIRED,
53 | 'Image file path/url'
54 | )
55 | ->addOption(
56 | 'threshold',
57 | 't',
58 | InputOption::VALUE_REQUIRED,
59 | 'Color threshold',
60 | 382.5
61 | )
62 | ->addOption(
63 | 'ratio',
64 | 'r',
65 | InputOption::VALUE_REQUIRED,
66 | 'Image resize ratio'
67 | )
68 | ->addOption(
69 | 'fab',
70 | 'f',
71 | InputOption::VALUE_NONE,
72 | 'Make the output fabulous'
73 | )
74 | ->addOption(
75 | 'invert',
76 | 'i',
77 | InputOption::VALUE_NONE,
78 | 'Invert colors'
79 | );
80 | }
81 |
82 | protected function execute(InputInterface $input, OutputInterface $output)
83 | {
84 | list($terminalWidth, $terminalHeight) = $this->getApplication()->getTerminalDimensions();
85 |
86 | $printer = new ImagePrinter(
87 | $input->getArgument('image'),
88 | $input->getOption('threshold'),
89 | $input->getOption('ratio'),
90 | $input->getOption('invert'),
91 | $input->getOption('fab')
92 | );
93 |
94 | $printer->run($terminalWidth, $terminalHeight);
95 | }
96 | }
97 |
98 | $console = new Application();
99 | $console->run();
--------------------------------------------------------------------------------
/examples/octocat2term.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 |
8 | *
9 | * For the full copyright and license information, please view the LICENSE
10 | * file that was distributed with this source code.
11 | */
12 |
13 | require_once __DIR__ . '/vendor/autoload.php';
14 |
15 | use Symfony\Component\Console\Application as ConsoleApplication;
16 | use Symfony\Component\Console\Command\Command as ConsoleCommand;
17 |
18 | use Symfony\Component\Console\Input\InputArgument;
19 | use Symfony\Component\Console\Input\InputOption;
20 | use Symfony\Component\Console\Input\InputDefinition;
21 | use Symfony\Component\Console\Input\InputInterface;
22 | use Symfony\Component\Console\Output\OutputInterface;
23 |
24 | use Goutte\Client;
25 |
26 | class Application extends ConsoleApplication
27 | {
28 | public function __construct() {
29 | parent::__construct(basename(__FILE__), '1.0');
30 | $this->add(new Command);
31 | }
32 |
33 | protected function getCommandName(InputInterface $input) {
34 | return basename(__FILE__);
35 | }
36 |
37 | protected function getDefaultInputDefinition()
38 | {
39 | return new InputDefinition(array(
40 | new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message.'),
41 | new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version.')
42 | ));
43 | }
44 | }
45 |
46 | class Command extends ConsoleCommand
47 | {
48 | protected function configure()
49 | {
50 | $this->setName(basename(__FILE__))
51 | ->setDescription('convert an octocat to terminal')
52 | ->addArgument(
53 | 'cat',
54 | InputArgument::REQUIRED,
55 | 'Cat number, name, title, or "random"'
56 | )
57 | ->addOption(
58 | 'threshold',
59 | 't',
60 | InputOption::VALUE_REQUIRED,
61 | 'Color threshold',
62 | 382.5
63 | )
64 | ->addOption(
65 | 'ratio',
66 | 'r',
67 | InputOption::VALUE_REQUIRED,
68 | 'Image resize ratio'
69 | )
70 | ->addOption(
71 | 'fab',
72 | 'f',
73 | InputOption::VALUE_NONE,
74 | 'Make the output fabulous'
75 | )
76 | ->addOption(
77 | 'invert',
78 | 'i',
79 | InputOption::VALUE_NONE,
80 | 'Invert colors'
81 | );
82 | }
83 |
84 | protected function execute(InputInterface $input, OutputInterface $output)
85 | {
86 | $url = 'https://octodex.github.com';
87 | $cat = $input->getArgument('cat');
88 |
89 | $client = new Client();
90 | $crawler = $client->request('GET', $url);
91 |
92 | try {
93 | if (is_numeric($cat) || $cat == 'random') {
94 | $filter = $crawler->filter('.preview-image > img');
95 | $total = iterator_count($filter);
96 |
97 | if ($cat == 'random') {
98 | $cat = mt_rand(1, $total);
99 | }
100 |
101 | $image = $filter->eq($total - $cat)->attr('data-src');
102 | }
103 |
104 | else if (substr($cat, 0, 4) == 'the ') {
105 | $image = $crawler->filter("img[alt=\"$cat\"]")->attr('data-src');
106 | }
107 |
108 | else {
109 | $image = $crawler->filter("a[href=\"/$cat\"] > img")->attr('data-src');
110 | }
111 | }
112 |
113 | catch (InvalidArgumentException $exception) {
114 | throw new RuntimeException('Octocat not found at: ' . $url);
115 | }
116 |
117 | list($terminalWidth, $terminalHeight) = $this->getApplication()->getTerminalDimensions();
118 |
119 | $printer = new ImagePrinter(
120 | 'https://octodex.github.com' . $image,
121 | $input->getOption('threshold'),
122 | $input->getOption('ratio'),
123 | $input->getOption('invert'),
124 | $input->getOption('fab')
125 | );
126 |
127 | $printer->run($terminalWidth, $terminalHeight);
128 | }
129 | }
130 |
131 | $console = new Application();
132 | $console->run();
133 |
--------------------------------------------------------------------------------
/examples/turtle.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | require_once __DIR__ . '/vendor/autoload.php';
13 |
14 | use Drawille\Turtle;
15 |
16 | $turtle = new Turtle();
17 |
18 | for ($x = 0; $x < 36; $x++) {
19 | $turtle->right(10);
20 |
21 | for ($y = 0; $y < 36; $y++) {
22 | $turtle->right(10);
23 | $turtle->forward(8);
24 | }
25 | }
26 |
27 | echo $turtle->frame(), "\n";
--------------------------------------------------------------------------------
/examples/xkcd2term.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 |
8 | *
9 | * For the full copyright and license information, please view the LICENSE
10 | * file that was distributed with this source code.
11 | */
12 |
13 | require_once __DIR__ . '/vendor/autoload.php';
14 |
15 | use Symfony\Component\Console\Application as ConsoleApplication;
16 | use Symfony\Component\Console\Command\Command as ConsoleCommand;
17 |
18 | use Symfony\Component\Console\Input\InputArgument;
19 | use Symfony\Component\Console\Input\InputOption;
20 | use Symfony\Component\Console\Input\InputDefinition;
21 | use Symfony\Component\Console\Input\InputInterface;
22 | use Symfony\Component\Console\Output\OutputInterface;
23 |
24 | use Goutte\Client;
25 |
26 | class Application extends ConsoleApplication
27 | {
28 | public function __construct() {
29 | parent::__construct(basename(__FILE__), '1.0');
30 | $this->add(new Command);
31 | }
32 |
33 | protected function getCommandName(InputInterface $input) {
34 | return basename(__FILE__);
35 | }
36 |
37 | protected function getDefaultInputDefinition()
38 | {
39 | return new InputDefinition(array(
40 | new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message.'),
41 | new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version.')
42 | ));
43 | }
44 | }
45 |
46 | class Command extends ConsoleCommand
47 | {
48 | protected function configure()
49 | {
50 | $this->setName(basename(__FILE__))
51 | ->setDescription('convert an xkcd comic to terminal')
52 | ->addArgument(
53 | 'comic',
54 | InputArgument::REQUIRED,
55 | 'Comic ID or "random"'
56 | )
57 | ->addOption(
58 | 'threshold',
59 | 't',
60 | InputOption::VALUE_REQUIRED,
61 | 'Color threshold',
62 | 382.5
63 | )
64 | ->addOption(
65 | 'ratio',
66 | 'r',
67 | InputOption::VALUE_REQUIRED,
68 | 'Image resize ratio'
69 | )
70 | ->addOption(
71 | 'fab',
72 | 'f',
73 | InputOption::VALUE_NONE,
74 | 'Make the output fabulous'
75 | )
76 | ->addOption(
77 | 'invert',
78 | 'i',
79 | InputOption::VALUE_NONE,
80 | 'Invert colors'
81 | );
82 | }
83 |
84 | protected function execute(InputInterface $input, OutputInterface $output)
85 | {
86 | $comic = $input->getArgument('comic');
87 | $url = $comic == 'random' ? 'http://c.xkcd.com/random/comic/' : "http://xkcd.com/$comic/";
88 |
89 | $client = new Client();
90 | $crawler = $client->request('GET', $url);
91 |
92 | try {
93 | $image = $crawler->filter('#comic > img')->attr('src');
94 | }
95 |
96 | catch(InvalidArgumentException $exception) {
97 | throw new RuntimeException('No comic found on: ' . $url);
98 | }
99 |
100 | list($terminalWidth, $terminalHeight) = $this->getApplication()->getTerminalDimensions();
101 |
102 | $printer = new ImagePrinter(
103 | $image,
104 | $input->getOption('threshold'),
105 | $input->getOption('ratio'),
106 | $input->getOption('invert'),
107 | $input->getOption('fab')
108 | );
109 |
110 | $printer->run($terminalWidth, $terminalHeight);
111 | }
112 | }
113 |
114 | $console = new Application();
115 | $console->run();
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 | tests
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/Drawille/Canvas.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Drawille;
13 |
14 | /**
15 | * Pixel surface
16 | *
17 | * @author Jeff Welch
18 | */
19 | class Canvas
20 | {
21 | /**
22 | * Dots:
23 | *
24 | * ,___,
25 | * |1 4|
26 | * |2 5|
27 | * |3 6|
28 | * |7 8|
29 | * `````
30 | *
31 | * @var array
32 | * @see http://www.alanwood.net/unicode/braille_patterns.html
33 | */
34 | private static $pixel_map = [
35 | [0x01, 0x08],
36 | [0x02, 0x10],
37 | [0x04, 0x20],
38 | [0x40, 0x80]
39 | ];
40 |
41 | /**
42 | * Braille characters starts at 0x2800
43 | *
44 | * @var integer
45 | */
46 | private static $braille_char_offset = 0x2800;
47 |
48 | /**
49 | * Canvas representation
50 | *
51 | * @var array
52 | */
53 | private $chars = [];
54 |
55 | /**
56 | * Clears the canvas
57 | */
58 | public function clear() {
59 | $this->chars = [];
60 | }
61 |
62 | /**
63 | * Sets a pixel at the given position
64 | *
65 | * @param integer $x x position
66 | * @param integer $y y position
67 | */
68 | public function set($x, $y) {
69 | list($x, $y, $px, $py) = $this->prime($x, $y);
70 | $this->chars[$py][$px] |= $this->getDotFromMap($x, $y);
71 | }
72 |
73 | /**
74 | * Unsets a pixel at the given position
75 | *
76 | * @param integer $x x position
77 | * @param integer $y y position
78 | */
79 | public function reset($x, $y) {
80 | list($x, $y, $px, $py) = $this->prime($x, $y);
81 | $this->chars[$py][$px] &= ~$this->getDotFromMap($x, $y);
82 | }
83 |
84 | /**
85 | * Gets the pixel state at a given position
86 | *
87 | * @param integer $x x position
88 | * @param integer $y y position
89 | *
90 | * @return bool the pixel state
91 | */
92 | public function get($x, $y) {
93 | list($x, $y, , , $char) = $this->prime($x, $y);
94 | return (bool) ($char & $this->getDotFromMap($x, $y));
95 | }
96 |
97 | /**
98 | * Toggles the pixel state on/off at a given position
99 | *
100 | * @param integer $x x position
101 | * @param integer $y y position
102 | */
103 | public function toggle($x, $y) {
104 | $this->get($x, $y) ? $this->reset($x, $y) : $this->set($x, $y);
105 | }
106 |
107 | /**
108 | * Gets a line
109 | *
110 | * @param integer $y y position
111 | * @param array $options options
112 | *
113 | * @return string line
114 | */
115 | public function row($y, array $options = []) {
116 | $row = isset($this->chars[$y]) ? $this->chars[$y] : [];
117 |
118 | if (!isset($options['min_x']) || !isset($options['max_x'])) {
119 | if (!($keys = array_keys($row))) {
120 | return '';
121 | }
122 | }
123 |
124 | $min = isset($options['min_x']) ? $options['min_x'] : min($keys);
125 | $max = isset($options['max_x']) ? $options['max_x'] : max($keys);
126 |
127 | return array_reduce(range($min, $max), function ($carry, $item) use ($row) {
128 | return $carry .= $this->toBraille(isset($row[$item]) ? $row[$item] : 0);
129 | }, '');
130 | }
131 |
132 | /**
133 | * Gets all lines
134 | *
135 | * @param array $options options
136 | *
137 | * @return array line
138 | */
139 | public function rows(array $options = []) {
140 | if (!isset($options['min_y']) || !isset($options['max_y'])) {
141 | if (!($keys = array_keys($this->chars))) {
142 | return [];
143 | }
144 | }
145 |
146 | $min = isset($options['min_y']) ? $options['min_y'] : min($keys);
147 | $max = isset($options['max_y']) ? $options['max_y'] : max($keys);
148 |
149 | if (!isset($options['min_x']) || !isset($options['max_x'])) {
150 | $flattened = array();
151 | foreach ($this->chars as $key => $char) {
152 | $flattened = array_merge($flattened, array_keys($char));
153 | }
154 | }
155 |
156 | $options['min_x'] = isset($options['min_x']) ? $options['min_x'] : min($flattened);
157 | $options['max_x'] = isset($options['max_x']) ? $options['max_x'] : max($flattened);
158 |
159 | return array_map(function ($i) use ($options) {
160 | return $this->row($i, $options);
161 | }, range($min, $max));
162 | }
163 |
164 | /**
165 | * Gets a string representation of the canvas
166 | *
167 | * @param array $options options
168 | *
169 | * @return string representation
170 | */
171 | public function frame(array $options = []) {
172 | return join("\n", $this->rows($options));
173 | }
174 |
175 | /**
176 | * Gets the canvas representation.
177 | *
178 | * @return array characters
179 | */
180 | public function getChars() {
181 | return $this->chars;
182 | }
183 |
184 | /**
185 | * Gets a braille unicode character
186 | *
187 | * @param integer $code character code
188 | *
189 | * @return string braille
190 | */
191 | private function toBraille($code) {
192 | return html_entity_decode('' . (self::$braille_char_offset + $code) . ';', ENT_NOQUOTES, 'UTF-8');
193 | }
194 |
195 | /**
196 | * Gets a dot from the pixel map.
197 | *
198 | * @param integer $x x position
199 | * @param integer $y y position
200 | *
201 | * @return integer dot
202 | */
203 | private function getDotFromMap($x, $y) {
204 | $y = $y % 4;
205 | $x = $x % 2;
206 |
207 | return self::$pixel_map[$y < 0 ? 4 + $y : $y][$x < 0 ? 2 + $x : $x];
208 | }
209 |
210 | /**
211 | * Autovivification for a canvas position.
212 | *
213 | * @param integer $x x position
214 | * @param integer $y y position
215 | *
216 | * @return array
217 | */
218 | private function prime($x, $y) {
219 | $x = round($x);
220 | $y = round($y);
221 | $px = floor($x / 2);
222 | $py = floor($y / 4);
223 |
224 | if (!isset($this->chars[$py][$px])) {
225 | $this->chars[$py][$px] = 0;
226 | }
227 |
228 | return [$x, $y, $px, $py, $this->chars[$py][$px]];
229 | }
230 | }
--------------------------------------------------------------------------------
/src/Drawille/Turtle.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Drawille;
13 |
14 | /**
15 | * Basic turtle graphics interface
16 | *
17 | * @author Jeff Welch
18 | * @see http://en.wikipedia.org/wiki/Turtle_graphics
19 | */
20 | class Turtle extends Canvas
21 | {
22 | /**
23 | * Current x position
24 | *
25 | * @var integer
26 | */
27 | private $x = 0;
28 | /**
29 | * Current y position
30 | *
31 | * @var integer
32 | */
33 | private $y = 0;
34 | /**
35 | * Current canvas rotation
36 | *
37 | * @var integer
38 | */
39 | private $rotation = 0;
40 |
41 | /**
42 | * If the pen is up
43 | *
44 | * @var boolean
45 | */
46 | private $up = false;
47 |
48 | /**
49 | * Constructor
50 | *
51 | * @param int $y starting x position
52 | * @param int $y starting y position
53 | */
54 | public function __construct($x = 0, $y = 0) {
55 | $this->x = $x;
56 | $this->y = $y;
57 | }
58 |
59 | /**
60 | * Gets the current x position.
61 | *
62 | * @return integer x position
63 | */
64 | public function getX() {
65 | return $this->x;
66 | }
67 |
68 | /**
69 | * Gets the current y position.
70 | *
71 | * @return integer y position
72 | */
73 | public function getY() {
74 | return $this->y;
75 | }
76 |
77 | /**
78 | * Gets the current canvas rotation
79 | *
80 | * @return integer current canvas rotation
81 | */
82 | public function getRotation() {
83 | return $this->rotation;
84 | }
85 |
86 | /**
87 | * Push the pen down
88 | */
89 | public function down() {
90 | $this->up = false;
91 | }
92 |
93 | /**
94 | * Pull the pen up
95 | */
96 | public function up() {
97 | $this->up = true;
98 | }
99 |
100 | /**
101 | * Move the pen forward
102 | *
103 | * @param integer $length distance to move forward
104 | */
105 | public function forward($length) {
106 | $theta = $this->rotation / 180.0 * M_PI;
107 | $x = $this->x + $length * cos($theta);
108 | $y = $this->y + $length * sin($theta);
109 |
110 | $this->move($x, $y);
111 | }
112 |
113 | /**
114 | * Move the pen backwards
115 | *
116 | * @param integer $length distance to move backwards
117 | */
118 | public function back($length) {
119 | $this->forward(-$length);
120 | }
121 |
122 | /**
123 | * Angle the canvas to the right.
124 | *
125 | * @param integer $angle degree to angle
126 | */
127 | public function right($angle) {
128 | $this->rotation += $angle;
129 | }
130 |
131 | /**
132 | * Angle the canvas to the left.
133 | *
134 | * @param integer $angle degree to angle
135 | */
136 | public function left($angle) {
137 | $this->rotation -= $angle;
138 | }
139 |
140 | /**
141 | * Move the pen, drawing if the pen is down.
142 | *
143 | * @param int $y new x position
144 | * @param int $y new y position
145 | */
146 | public function move($x, $y) {
147 | if (!$this->up) {
148 | $x1 = round($this->x);
149 | $y1 = round($this->y);
150 | $x2 = $x;
151 | $y2 = $y;
152 |
153 | $xdiff = max($x1, $x2) - min($x1, $x2);
154 | $ydiff = max($y1, $y2) - min($y1, $y2);
155 |
156 | $xdir = $x1 <= $x2 ? 1 : -1;
157 | $ydir = $y1 <= $y2 ? 1 : -1;
158 |
159 | $r = max($xdiff, $ydiff);
160 |
161 | for ($i = 0; $i <= $r; $i++) {
162 | $x = $x1;
163 | $y = $y1;
164 |
165 | if ($ydiff > 0) {
166 | $y += ((float)$i * $ydiff) / $r * $ydir;
167 | }
168 |
169 | if ($xdiff > 0) {
170 | $x += ((float)$i * $xdiff) / $r * $xdir;
171 | }
172 |
173 | $this->set($x, $y);
174 | }
175 | }
176 |
177 | $this->x = $x;
178 | $this->y = $y;
179 | }
180 |
181 | /**
182 | * Pull the pen up
183 | */
184 | public function pu() {
185 | $this->up();
186 | }
187 |
188 | /**
189 | * Push the pen up
190 | */
191 | public function pd() {
192 | $this->down();
193 | }
194 |
195 | /**
196 | * Move the pen forward
197 | *
198 | * @param integer $length distance to move forward
199 | */
200 | public function fd($length) {
201 | $this->forward($length);
202 | }
203 |
204 | /**
205 | * Move the pen, drawing if the pen is down.
206 | *
207 | * @param int $y new x position
208 | * @param int $y new y position
209 | */
210 | public function mv($x, $y) {
211 | $this->move($x, $y);
212 | }
213 |
214 | /**
215 | * Angle the canvas to the right.
216 | *
217 | * @param integer $angle degree to angle
218 | */
219 | public function rt($angle) {
220 | $this->right($angle);
221 | }
222 |
223 | /**
224 | * Angle the canvas to the left.
225 | *
226 | * @param integer $angle degree to angle
227 | */
228 | public function lt($angle) {
229 | $this->left($angle);
230 | }
231 |
232 | /**
233 | * Move the pen backwards
234 | *
235 | * @param integer $length distance to move backwards
236 | */
237 | public function bk($length) {
238 | $this->back($length);
239 | }
240 | }
--------------------------------------------------------------------------------
/tests/Drawille/CanvasTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Drawille;
13 |
14 | class CanvasTest extends \PHPUnit_Framework_TestCase
15 | {
16 | private $canvas;
17 |
18 | protected function setUp() {
19 | $this->canvas = new Canvas();
20 | }
21 |
22 | public function testSet() {
23 | $this->canvas->set(0, 0);
24 | $this->assertEquals([[1]], $this->canvas->getChars());
25 | }
26 |
27 | /**
28 | * @depends testSet
29 | */
30 | public function testReset() {
31 | $this->canvas->set(0, 0);
32 | $this->canvas->reset(0, 0);
33 | $this->assertEquals([[0]], $this->canvas->getChars());
34 | }
35 |
36 | /**
37 | * @depends testSet
38 | */
39 | public function testClear() {
40 | $this->canvas->set(0, 0);
41 | $this->canvas->clear();
42 | $this->assertEmpty($this->canvas->getChars());
43 | }
44 |
45 | public function testToggle() {
46 | $this->canvas->toggle(0, 0);
47 | $this->assertEquals([[1]], $this->canvas->getChars());
48 |
49 | $this->canvas->toggle(0, 0);
50 | $this->assertEquals([[0]], $this->canvas->getChars());
51 | }
52 |
53 | /**
54 | * @depends testSet
55 | */
56 | public function testFrame() {
57 | $this->assertEquals($this->canvas->frame(), '');
58 |
59 | $this->canvas->set(0, 0);
60 | $this->assertEquals($this->canvas->frame(), '⠁');
61 | }
62 |
63 | /**
64 | * @depends testSet
65 | */
66 | public function testGet() {
67 | $this->assertFalse($this->canvas->get(0, 0));
68 | $this->canvas->set(0, 0);
69 |
70 | $this->assertTrue($this->canvas->get(0, 0));
71 | $this->assertFalse($this->canvas->get(1, 0));
72 | $this->assertFalse($this->canvas->get(0, 1));
73 | $this->assertFalse($this->canvas->get(1, 1));
74 | }
75 | }
--------------------------------------------------------------------------------
/tests/Drawille/TurtleTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Drawille;
13 |
14 | class TurtleTest extends \PHPUnit_Framework_TestCase
15 | {
16 | private $turtle;
17 |
18 | protected function setUp() {
19 | $this->turtle = new Turtle();
20 | }
21 |
22 | public function testPosition() {
23 | $this->assertEquals(0, $this->turtle->getX());
24 | $this->assertEquals(0, $this->turtle->getY());
25 |
26 | $this->turtle->move(1, 2);
27 | $this->assertEquals(1, $this->turtle->getX());
28 | $this->assertEquals(2, $this->turtle->getY());
29 | }
30 |
31 | public function testRotation() {
32 | $this->assertEquals(0, $this->turtle->getRotation());
33 |
34 | $this->turtle->right(30);
35 | $this->assertEquals(30, $this->turtle->getRotation());
36 |
37 | $this->turtle->left(30);
38 | $this->assertEquals(0, $this->turtle->getRotation());
39 | }
40 |
41 | public function testBrush() {
42 | $this->assertFalse($this->turtle->get($this->turtle->getX(), $this->turtle->getY()));
43 |
44 | $this->turtle->forward(1);
45 | $this->assertTrue($this->turtle->get(0, 0));
46 | $this->assertTrue($this->turtle->get($this->turtle->getX(), $this->turtle->getY()));
47 |
48 | $this->turtle->up();
49 | $this->turtle->move(2, 0);
50 | $this->assertFalse($this->turtle->get($this->turtle->getX(), $this->turtle->getY()));
51 |
52 | $this->turtle->down();
53 | $this->turtle->move(3, 0);
54 | $this->assertTrue($this->turtle->get($this->turtle->getX(), $this->turtle->getY()));
55 | }
56 | }
--------------------------------------------------------------------------------