├── .gitignore ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin └── catpix ├── catpix.gemspec └── lib ├── catpix.rb └── catpix ├── private.rb └── version.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | *.bundle 19 | *.so 20 | *.o 21 | *.a 22 | mkmf.log 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.0 (2015-07-04) 2 | 3 | New features: 4 | 5 | - Quadrupled the resolution of the image 6 | - Added line buffering 7 | - Drawing margins is slightly quicker 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in catpix.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Radek Pazdera 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Catpix 2 | 3 | [![Gem Version](https://badge.fury.io/rb/catpix.png)](http://badge.fury.io/rb/catpix) 4 | [![Inline docs](http://inch-ci.org/github/pazdera/catpix.png)](http://inch-ci.org/github/pazdera/catpix) 5 | 6 | Renders images in the terminal. 7 | 8 | ![Pokemon](http://radek.io/assets/images/posts/catpix/pokemon.png) 9 | 10 | It will handle most image formats (png, jpg, gif, bpm and many more). As long 11 | as ImageMagick can read it, catpix can too. By default, it will scale them 12 | down to fit the width of your terminal. You can set the same for the height 13 | of the image and also center it by providing custom options (see Usage below). 14 | 15 | On the inside, catpix uses [rmagick](https://rubygems.org/gems/rmagick) to read 16 | and scale images and the [tco](https://github.com/pazdera/tco) gem to map its 17 | colours to the extended 256 colour palette in the terminal. A pixel is 18 | approximated as two spaces, so you might get weird results in case your font 19 | has different proportions. 20 | 21 | It can render full resolution photos, although it takes a while and the 22 | resolution is obviously limited. Here's a photo of some flowers in my window, 23 | straight from my phone and rendered in the terminal: 24 | 25 | ![Full photo](http://radek.io/assets/images/posts/catpix/photo.png) 26 | 27 | ### Resolutions (new in 0.2.0) 28 | 29 | Catpix can render images in two resolutions. If you terminal supports 30 | unicode, it will use the 31 | [upper half block](http://www.fileformat.info/info/unicode/char/2580/index.htm) 32 | to dispplay one pixel. Otherwise, a pixel will be approximated as two 33 | spaces. The detection is automatic, but you can also force one or the 34 | other via _options_. The higher setting has four times as many pixels as the 35 | lower one. See how do they compare below: 36 | 37 | ![Comparing resolutions](http://radek.io/assets/images/posts/catpix/resolution.gif) 38 | 39 | ## Usage 40 | 41 | ### In the terminal 42 | 43 | The gem will install the `catpix` command on your system that you can use 44 | directly from shell. To print an image simply pass the path to it as the 45 | first argument: 46 | 47 | $ catpix pokemon.gif 48 | 49 | Use the `-c` flag to center it (x for horizontal and y for vertical centering): 50 | 51 | $ catpix panda.png -c xy 52 | 53 | Add `-w` or `-h` to scale it down. These two options require a factor of the 54 | size of your terminal. If you want to limit the size of your image to half of 55 | your terminal window use: 56 | 57 | $ catpix trophy.png -w 0.5 -h 0.5 58 | 59 | And finally, if your image has any fully transparent pixels, you can specify 60 | background colour to be rendered behind and around the image. Use `-b` to 61 | specify the colour and `-f` to make it fill the margins around the image if 62 | it's centered: 63 | 64 | $ catpix tux.png -b "#00ff00" # RGB is fine 65 | $ catpix tux.png -b green # tco aliases work too 66 | $ catpix tux.png -c xy -b green -f # fill the margins around the image too 67 | $ catpix tux.png -c xy -r high # enforce high resolution 68 | 69 | ### In Ruby 70 | 71 | The Ruby API consists of only a single function called `print_image`: 72 | 73 | ```ruby 74 | require 'catpix' 75 | 76 | Catpix::print_image "pokemon.png", 77 | :limit_x => 1.0, 78 | :limit_y => 0, 79 | :center_x => true, 80 | :center_y => true, 81 | :bg => "white", 82 | :bg_fill => true, 83 | :resolution => "low" 84 | ``` 85 | 86 | See the [documentation at RubyDoc](http://www.rubydoc.info/github/pazdera/catpix/master/Catpix.print_image) 87 | for more detail. 88 | 89 | ## More examples 90 | 91 | ![UK Flag](http://radek.io/assets/images/posts/catpix/flag.png) 92 | 93 | ![Happy Panda](http://radek.io/assets/images/posts/catpix/panda.png) 94 | 95 | ![Trophy](http://radek.io/assets/images/posts/catpix/trophy.png) 96 | 97 | ## Installation 98 | 99 | Use **gem** to install Catpix from [RubyGems](https://rubygems.org/gems/catpix): 100 | 101 | $ gem install catpix 102 | 103 | If using [bundler](http://bundler.io/), add this line to your application's 104 | Gemfile: 105 | 106 | gem 'catpix' 107 | 108 | And then execute: 109 | 110 | $ bundle 111 | 112 | ## Author 113 | 114 | Radek Pazdera <me@radek.io> [radek.io](http://radek.io/) 115 | 116 | ## Contributing 117 | 118 | 1. Fork it ( https://github.com/[my-github-username]/catpix/fork ) 119 | 2. Create your feature branch (`git checkout -b my-new-feature`) 120 | 3. Commit your changes (`git commit -am 'Add some feature'`) 121 | 4. Push to the branch (`git push origin my-new-feature`) 122 | 5. Create a new Pull Request 123 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | -------------------------------------------------------------------------------- /bin/catpix: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Copyright (c) 2015 Radek Pazdera 4 | # Distributed under the MIT License (see LICENSE.txt) 5 | 6 | require 'docopt' 7 | require 'catpix' 8 | 9 | doc = < 15 | 16 | Options: 17 | -w=, --limit-width Limit width of the image (factor of the size 18 | of the terminal window) [default: 1]. 19 | -h=, --limit-height Limit height of the image (factor of the size 20 | of the terminal window) [default: 0], 21 | -c=, --center Set x, y or xy centering in the window. 22 | -b=, --bg Set background colour. 23 | -f, --bg-fill Draw background around the image as well. 24 | -r=, --resolution Either 'high' or 'low' [default: auto]. 25 | 26 | --help Show this message. 27 | --version Print the version. 28 | DOCOPT 29 | 30 | begin 31 | args = Docopt::docopt doc, :version => Catpix::VERSION 32 | rescue Docopt::Exit => e 33 | $stderr.puts e.message 34 | exit 1 35 | end 36 | 37 | options = { 38 | :limit_x => args['--limit-width'].to_f, 39 | :limit_y => args['--limit-height'].to_f, 40 | :bg_fill => args['--bg-fill'], 41 | :resolution => args['--resolution'] 42 | } 43 | 44 | if args['--center'] 45 | options[:center_x] = true if args['--center'].include? 'x' 46 | options[:center_y] = true if args['--center'].include? 'y' 47 | 48 | if not args['--center'].include? 'x' or not args['--center'].include? 'x' 49 | $stderr.puts "Error: Invalid --center argument. Use x, y or xy." 50 | $stderr.puts doc 51 | end 52 | end 53 | 54 | options[:bg] = args['--bg'] if args['--bg'] 55 | 56 | Catpix::print_image args[''], options 57 | exit 0 58 | -------------------------------------------------------------------------------- /catpix.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'catpix/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "catpix" 8 | spec.version = Catpix::VERSION 9 | spec.authors = ["Radek Pazdera"] 10 | spec.email = ["me@radek.io"] 11 | spec.summary = %q{Cat images into the terminal.} 12 | spec.description = %q{Print images (png, jpg, gif and many others) in the 13 | command line with ease. Using rmagick and tco in the 14 | background to read the images and convert them into 15 | the extended 256 colour palette for terminals.} 16 | spec.homepage = "https://github.com/pazdera/catpix" 17 | spec.license = "MIT" 18 | 19 | spec.files = `git ls-files -z`.split("\x0") 20 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 21 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 22 | spec.require_paths = ["lib"] 23 | 24 | spec.required_ruby_version = ">= 1.9" 25 | 26 | spec.add_dependency "tco", "~> 0.1", ">= 0.1.8" 27 | spec.add_dependency "rmagick", "~> 2.15", ">= 2.15.2" 28 | spec.add_dependency "docopt", "~> 0.5", ">= 0.5.0" 29 | spec.add_dependency "ruby-terminfo", "~> 0.1", ">= 0.1.1" 30 | 31 | spec.add_development_dependency "bundler", "~> 1.6" 32 | spec.add_development_dependency "rake", "~> 10.4" 33 | end 34 | -------------------------------------------------------------------------------- /lib/catpix.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Radek Pazdera 2 | # Distributed under the MIT License (see LICENSE.txt) 3 | 4 | require "catpix/version" 5 | require "catpix/private" 6 | 7 | # Provides a function to print images in the terminal. A range of different 8 | # formats is supported (check out what ImageMagick supports). Under the hood, 9 | # this module uses two components: 10 | # 11 | # * [rmagick](https://rmagick.github.io/) to read and scale the images and 12 | # * [tco](https://github.com/pazdera/tco) to map their pixels on the extended 13 | # colour palette in the terminal. 14 | # 15 | # Some other minor features like centering and handling background colours 16 | # are supplied directly by this module. 17 | module Catpix 18 | # Print an image to the terminal. 19 | # 20 | # All formats supported by ImageMagick are supported. The image's colours 21 | # will be mapped onto the extended 256 colour palette. Also by default, it 22 | # will be scaled down to fit the width of the terminal while keeping its 23 | # proportions. This can be changed using the `options` parameter. 24 | # 25 | # @param [Hash] options Adjust some parameters of the image when printed. 26 | # @option options [Float] :limit_x A factor of the terminal window's width. 27 | # If present, the image will be scaled down 28 | # to fit (proportions are kept). Using 0 29 | # will disable the scaling. [default: 1.0] 30 | # @option options [Float] :limit_y A factor of the terminal window's height. 31 | # If present, the image will be scaled down 32 | # to fit (proportions are kept). Using 0 33 | # will disable the scaling. [default: 0] 34 | # @option options [Boolean] :center_x Center the image horizontally in the 35 | # terminal window. [default: false] 36 | # @option options [Boolean] :center_y Center the image vertically in the 37 | # terminal window. [default: false] 38 | # @option options [String] :bg Background colour to use in case there are 39 | # any fully transparent pixels in the image. 40 | # This can be a RGB value '#c0ffee' or a tco 41 | # alias 'red' or 'blue'. [default: nil] 42 | # @option options [Boolean] :bg_fill Fill the margins around the image with 43 | # background colour. [default: false] 44 | # @option options [String] :resolution Determines the pixel size of the 45 | # rendered image. Can be set to `high`, 46 | # `low` or `auto` (default). If set to 47 | # `auto` the resolution will be picked 48 | # automatically based on your terminal's 49 | # support of unicode. 50 | def self.print_image(path, options={}) 51 | options = default_options.merge! options 52 | 53 | if options[:resolution] == 'auto' 54 | options[:resolution] = can_use_utf8? ? 'high' : 'low' 55 | end 56 | @@resolution = options[:resolution] 57 | 58 | img = load_image path 59 | resize! img, options[:limit_x], options[:limit_y] 60 | 61 | margins = get_margins img, options[:center_x], options[:center_y] 62 | margins[:colour] = options[:bg_fill] ? options[:bg] : nil 63 | 64 | if high_res? 65 | do_print_image_hr img, margins, options 66 | else 67 | do_print_image_lr img, margins, options 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/catpix/private.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Radek Pazdera 2 | # Distributed under the MIT License (see LICENSE.txt) 3 | 4 | require "rmagick" 5 | require "tco" 6 | require "terminfo" 7 | 8 | module Catpix 9 | private 10 | MAX_OPACITY = 65535 11 | 12 | def self.default_options 13 | { 14 | limit_x: 1.0, 15 | limit_y: 0, 16 | center_x: false, 17 | center_y: false, 18 | bg: nil, 19 | bg_fill: false, 20 | resolution: 'auto' 21 | } 22 | end 23 | 24 | @@resolution = nil 25 | 26 | def self.high_res? 27 | @@resolution == 'high' 28 | end 29 | 30 | def self.can_use_utf8? 31 | ENV.values_at("LC_ALL", "LC_CTYPE", "LANG").compact.first.include?("UTF-8") 32 | end 33 | 34 | def self.prep_lr_pixel(colour) 35 | colour ? " ".bg(colour) : " " 36 | end 37 | 38 | def self.print_lr_pixel(colour) 39 | print prep_lr_pixel colour 40 | end 41 | 42 | def self.prep_hr_pixel(colour_top, colour_bottom) 43 | upper = "\u2580" 44 | lower = "\u2584" 45 | 46 | return " " if colour_bottom.nil? and colour_top.nil? 47 | return lower.fg colour_bottom if colour_top.nil? 48 | return upper.fg colour_top if colour_bottom.nil? 49 | 50 | c_top = Tco::match_colour colour_top 51 | c_bottom = Tco::match_colour colour_bottom 52 | if c_top == c_bottom 53 | return " ".bg "@#{c_top}" 54 | end 55 | 56 | upper.fg("@#{c_top}").bg("@#{c_bottom}") 57 | end 58 | 59 | def self.print_hr_pixel(colour_top, colour_bottom) 60 | print prep_hr_pixel colour_top, colour_bottom 61 | end 62 | 63 | # Returns normalised size of the terminal window 64 | # 65 | # Catpix can use either two blank spaces to approximate a pixel in the 66 | # temrinal or the 'upper half block' and 'bottom half block' characters. 67 | # 68 | # Depending on which of the above will be used, the screen size 69 | # must be normalised accordingly. 70 | def self.get_screen_size 71 | th, tw = TermInfo.screen_size 72 | if high_res? then [tw, th * 2] else [tw / 2, th] end 73 | end 74 | 75 | def self.load_image(path) 76 | Magick::Image::read(path).first 77 | end 78 | 79 | # Scale the image down based on the limits while keeping the aspect ratio 80 | def self.resize!(img, limit_x=0, limit_y=0) 81 | tw, th = get_screen_size 82 | iw = img.columns 83 | ih = img.rows 84 | 85 | width = if limit_x > 0 86 | (tw * limit_x).to_i 87 | else 88 | iw 89 | end 90 | 91 | height = if limit_y > 0 92 | (th * limit_y).to_i 93 | else 94 | ih 95 | end 96 | 97 | # Resize the image if it's bigger than the limited viewport 98 | if iw > width or ih > height 99 | img.change_geometry "#{width}x#{height}" do |cols, rows, img_handle| 100 | img_handle.resize! (cols).to_i, (rows).to_i 101 | end 102 | end 103 | end 104 | 105 | # Returns the normalised RGB of a ImageMagick's pixel 106 | def self.get_normal_rgb(pixel) 107 | [pixel.red, pixel.green, pixel.blue].map { |v| 255*(v/65535.0) } 108 | end 109 | 110 | # Determine the margins based on the centering options 111 | def self.get_margins(img, center_x, center_y) 112 | margins = {} 113 | tw, th = get_screen_size 114 | 115 | x_space = tw - img.columns 116 | if center_x 117 | margins[:left] = x_space / 2 118 | margins[:right] = x_space / 2 + x_space % 2 119 | else 120 | margins[:left] = 0 121 | margins[:right] = x_space 122 | end 123 | 124 | y_space = th - img.rows 125 | if center_y 126 | margins[:top] = y_space / 2 127 | margins[:bottom] = y_space / 2 + y_space % 2 128 | else 129 | margins[:top] = 0 130 | margins[:bottom] = 0 131 | end 132 | 133 | if high_res? and margins[:top] % 2 and margins[:bottom] % 2 134 | margins[:top] -= 1 135 | margins[:bottom] += 1 136 | end 137 | 138 | margins 139 | end 140 | 141 | def self.prep_vert_margin(size, colour) 142 | tw, th = get_screen_size 143 | 144 | buffer = "" 145 | if high_res? 146 | (size / 2).times do 147 | sub_buffer = "" 148 | tw.times { sub_buffer += prep_hr_pixel nil, nil } 149 | buffer += sub_buffer.bg(colour) + "\n" 150 | end 151 | else 152 | size.times do 153 | sub_buffer = "" 154 | tw.times { sub_buffer += prep_lr_pixel nil } 155 | buffer += sub_buffer.bg(colour) + "\n" 156 | end 157 | end 158 | buffer 159 | end 160 | 161 | def self.prep_horiz_margin(size, colour) 162 | buffer = "" 163 | if high_res? 164 | size.times { buffer += prep_hr_pixel nil, nil } 165 | else 166 | size.times { buffer += prep_lr_pixel nil } 167 | end 168 | buffer.bg colour 169 | end 170 | 171 | # Print the image in low resolution 172 | def self.do_print_image_lr(img, margins, options) 173 | print prep_vert_margin margins[:top], margins[:colour] 174 | 175 | 0.upto(img.rows - 1) do |row| 176 | buffer = prep_horiz_margin margins[:left], margins[:colour] 177 | 0.upto(img.columns - 1) do |col| 178 | pixel = img.pixel_color col, row 179 | 180 | buffer += if pixel.opacity == MAX_OPACITY 181 | prep_lr_pixel options[:bg] 182 | else 183 | prep_lr_pixel get_normal_rgb pixel 184 | end 185 | end 186 | buffer += prep_horiz_margin margins[:right], margins[:colour] 187 | puts buffer 188 | end 189 | 190 | print prep_vert_margin margins[:bottom], margins[:colour] 191 | end 192 | 193 | # Print the image in high resolution (using unicode's upper half block) 194 | def self.do_print_image_hr(img, margins, options) 195 | print prep_vert_margin margins[:top], margins[:colour] 196 | 197 | 0.step(img.rows - 1, 2) do |row| 198 | # line buffering makes it about 20% faster 199 | buffer = prep_horiz_margin margins[:left], margins[:colour] 200 | 0.upto(img.columns - 1) do |col| 201 | top_pixel = img.pixel_color col, row 202 | colour_top = if top_pixel.opacity < MAX_OPACITY 203 | get_normal_rgb top_pixel 204 | else 205 | options[:bg] 206 | end 207 | 208 | bottom_pixel = img.pixel_color col, row + 1 209 | colour_bottom = if bottom_pixel.opacity < MAX_OPACITY 210 | get_normal_rgb bottom_pixel 211 | else 212 | options[:bg] 213 | end 214 | 215 | buffer += prep_hr_pixel colour_top, colour_bottom 216 | end 217 | buffer += prep_horiz_margin margins[:right], margins[:colour] 218 | puts buffer 219 | end 220 | 221 | print prep_vert_margin margins[:bottom], margins[:colour] 222 | end 223 | end 224 | -------------------------------------------------------------------------------- /lib/catpix/version.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Radek Pazdera 2 | # Distributed under the MIT License (see LICENSE.txt) 3 | 4 | module Catpix 5 | # Version of the gem 6 | VERSION = "0.2.0" 7 | end 8 | --------------------------------------------------------------------------------