branding 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +33 -0
- data/Rakefile +6 -0
- data/bin/branding +6 -0
- data/branding.gemspec +26 -0
- data/lib/branding.rb +12 -0
- data/lib/branding/ansi.rb +80 -0
- data/lib/branding/canvas.rb +37 -0
- data/lib/branding/cli.rb +37 -0
- data/lib/branding/logo.rb +14 -0
- data/lib/branding/pixel.rb +133 -0
- data/lib/branding/png.rb +236 -0
- data/lib/branding/railtie.rb +23 -0
- data/lib/branding/version.rb +3 -0
- data/spec/branding_spec.rb +7 -0
- data/spec/pixel_spec.rb +11 -0
- data/spec/png_spec.rb +14 -0
- data/spec/spec_helper.rb +6 -0
- metadata +142 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3688ed4157906260e426018c08b5236dbcf7a75b
|
4
|
+
data.tar.gz: 3914f9b09f648a42dc171727409fb66ac35867f5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ad2c13b8bba0648fd37e98a19e36e83ab17314ba96570738417d3d4257559eb018e3f499976676de023117afa4be2d23163dc220aad4611ba953fa4f804266a9
|
7
|
+
data.tar.gz: 853164e000f97ce4101213e664eec010a05bce557c099f2fdc116f7ff7cd46dc618f9a59ddc803e3620b43ba4ac6386723ce80d5c4bd053e7d4fb57487c5a5ec
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2016 Stefan Natchev
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# Branding
|
2
|
+
|
3
|
+
Proud of your code? Add some bling to your terminal.
|
4
|
+
|
5
|
+
## Perks
|
6
|
+
|
7
|
+
* No dependencies!
|
8
|
+
* Pure Ruby PNG decoding in < 200 LoC
|
9
|
+
* Loads of fun!
|
10
|
+
|
11
|
+
## Quality
|
12
|
+
|
13
|
+
Much quality:
|
14
|
+
|
15
|
+
## How it works
|
16
|
+
|
17
|
+
Branding will detect your Rails application's favicon. It adds a small
|
18
|
+
initializer in development and testing environments that draws the favicon with
|
19
|
+
ANSI control characters.
|
20
|
+
|
21
|
+
## Installation
|
22
|
+
|
23
|
+
Add this line to your application's Gemfile:
|
24
|
+
|
25
|
+
gem 'branding'
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
Continue your normal workflow.
|
30
|
+
|
31
|
+
## Contributing
|
32
|
+
|
33
|
+
Have a good idea to make Branding more awesome? Make a pull request and share your thoughts!
|
data/Rakefile
ADDED
data/bin/branding
ADDED
data/branding.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'branding/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'branding'
|
8
|
+
spec.version = Branding::VERSION
|
9
|
+
spec.authors = ['Stefan Natchev']
|
10
|
+
spec.email = ['stefan.natchev@gmail.com']
|
11
|
+
spec.summary = 'Print your logo to the terminal.'
|
12
|
+
spec.description = 'Proud of your work? Add some branding bling with Branding.rb'
|
13
|
+
spec.homepage = 'https://github.com/snatchev/branding.rb'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.5'
|
22
|
+
spec.add_development_dependency 'pry', '~> 0.10'
|
23
|
+
spec.add_development_dependency 'rake', '~> 11.1'
|
24
|
+
spec.add_development_dependency 'rspec', '~> 3.4'
|
25
|
+
spec.add_development_dependency 'rubocop', '~> 0.40'
|
26
|
+
end
|
data/lib/branding.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'branding/ansi'
|
2
|
+
require 'branding/canvas'
|
3
|
+
require 'branding/cli'
|
4
|
+
require 'branding/logo'
|
5
|
+
require 'branding/pixel'
|
6
|
+
require 'branding/png'
|
7
|
+
require 'branding/version'
|
8
|
+
require 'branding/railtie' if defined?(Rails)
|
9
|
+
|
10
|
+
module Branding
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Branding
|
2
|
+
module ANSI
|
3
|
+
ATTRS = (0..8).map{|i| "\e[#{i}m".to_sym }.freeze
|
4
|
+
FGCOLORS = (0..256).map{|i| "\e[38;5;#{i}m".to_sym }.freeze
|
5
|
+
BGCOLORS = (0..256).map{|i| "\e[48;5;#{i}m".to_sym }.freeze
|
6
|
+
#2580 - 259F
|
7
|
+
SHADERS = [:"\u2591", :"\u2592", :"\u2593"].freeze
|
8
|
+
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def fg(r,g,b)
|
12
|
+
FGCOLORS[rgb_offset(r,g,b)]
|
13
|
+
end
|
14
|
+
|
15
|
+
def bg(r,g,b)
|
16
|
+
BGCOLORS[rgb_offset(r,g,b)]
|
17
|
+
end
|
18
|
+
|
19
|
+
def reset
|
20
|
+
ATTRS[0]
|
21
|
+
end
|
22
|
+
|
23
|
+
def clear
|
24
|
+
end
|
25
|
+
|
26
|
+
def up
|
27
|
+
end
|
28
|
+
|
29
|
+
def down
|
30
|
+
end
|
31
|
+
|
32
|
+
def left
|
33
|
+
end
|
34
|
+
|
35
|
+
def right
|
36
|
+
end
|
37
|
+
|
38
|
+
def save_cursor
|
39
|
+
:"\e[s"
|
40
|
+
end
|
41
|
+
|
42
|
+
def restore_cursor
|
43
|
+
:"\e[u"
|
44
|
+
end
|
45
|
+
|
46
|
+
# 0x10-0xE7: 6 × 6 × 6 = 216 colors
|
47
|
+
def rgb_offset(r,g,b)
|
48
|
+
16 + (36 * scale_color(r)) + (6 * scale_color(g)) + scale_color(b)
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# scale an 8bit number to 0-5
|
53
|
+
# 5*51==255
|
54
|
+
def scale_color(uint8)
|
55
|
+
(uint8 / 51.0).round
|
56
|
+
end
|
57
|
+
|
58
|
+
# we probably shouldn't be passing in non-ints
|
59
|
+
def uint32_to_rgb(uint32)
|
60
|
+
return [0,0,0] unless uint32.is_a?(Integer)
|
61
|
+
|
62
|
+
r = (uint32 & 0xff000000) >> 24
|
63
|
+
g = (uint32 & 0x00ff0000) >> 16
|
64
|
+
b = (uint32 & 0x0000ff00) >> 8
|
65
|
+
|
66
|
+
[r,g,b]
|
67
|
+
end
|
68
|
+
|
69
|
+
# we probably shouldn't be passing in non-ints
|
70
|
+
def clamped(uint32)
|
71
|
+
return [0,0,0] unless uint32.is_a?(Integer)
|
72
|
+
|
73
|
+
r = (uint32 & 0xff000000) >> 24
|
74
|
+
g = (uint32 & 0x00ff0000) >> 16
|
75
|
+
b = (uint32 & 0x0000ff00) >> 8
|
76
|
+
|
77
|
+
scale_color(r) & scale_color(g) & scale_color(b)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'branding/ansi'
|
2
|
+
|
3
|
+
module Branding
|
4
|
+
class Canvas
|
5
|
+
attr_reader :width, :height, :rows, :cols
|
6
|
+
|
7
|
+
def initialize(width:,height:)
|
8
|
+
@width, @height = width, height
|
9
|
+
@rows, @cols = `stty size`.split.map { |x| x.to_i }
|
10
|
+
@pixel_buffer = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def load(pixels)
|
14
|
+
Pixel2x.load_strategy(pixels, width: width, height: height) do |pixel|
|
15
|
+
@pixel_buffer << pixel
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def print
|
20
|
+
@pixel_buffer.each_with_index do |pixel, idx|
|
21
|
+
if (idx % width * pixel.width) >= cols
|
22
|
+
next
|
23
|
+
end
|
24
|
+
|
25
|
+
if idx % max_width == 0
|
26
|
+
STDOUT.puts(ANSI.reset)
|
27
|
+
end
|
28
|
+
|
29
|
+
STDOUT.print(pixel)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def max_width
|
34
|
+
@max_width ||= [@width, @cols].min
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/branding/cli.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module Branding
|
5
|
+
class CLI
|
6
|
+
def initialize(args)
|
7
|
+
@options = OpenStruct.new
|
8
|
+
|
9
|
+
@options.command = :print_logo
|
10
|
+
@options.file = args.last
|
11
|
+
|
12
|
+
@parser = OptionParser.new do |opts|
|
13
|
+
opts.banner = 'Usage: branding FILE'
|
14
|
+
opts.on('-a') do |algo|
|
15
|
+
@options = algo
|
16
|
+
end
|
17
|
+
|
18
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
19
|
+
puts opts
|
20
|
+
exit
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
@parser.parse!(args)
|
25
|
+
@options
|
26
|
+
end
|
27
|
+
|
28
|
+
def run
|
29
|
+
send(@options.command)
|
30
|
+
end
|
31
|
+
|
32
|
+
def print_logo
|
33
|
+
logo = Branding::Logo.new(@options.file)
|
34
|
+
logo.print
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'matrix'
|
3
|
+
|
4
|
+
module Branding
|
5
|
+
##
|
6
|
+
#
|
7
|
+
#
|
8
|
+
class Pixel
|
9
|
+
def self.load_strategy(pixels, height:, width:)
|
10
|
+
pixels.each do |pixel_value|
|
11
|
+
yield self.new(pixel_value)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.rgb(r,g,b)
|
16
|
+
self.new((r << 24) | (g << 16) | b << 8)
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_accessor :uint32
|
20
|
+
|
21
|
+
##
|
22
|
+
# basic 2-space with background color per pixel
|
23
|
+
def initialize(*opts)
|
24
|
+
opts = opts.first if opts.size == 1
|
25
|
+
|
26
|
+
case opts
|
27
|
+
when self.class
|
28
|
+
@uint32 = opts.uint32
|
29
|
+
when Fixnum
|
30
|
+
@uint32 = opts
|
31
|
+
when Array
|
32
|
+
r,g,b,a = opts
|
33
|
+
a ||= 0xff # alpha is optional. If it is not supplied, assume full-alpha.
|
34
|
+
@uint32 = (r << 24) | (g << 16) | b << 8 | a
|
35
|
+
when Hash
|
36
|
+
#(r << 24) | (g << 16) | b << 8)
|
37
|
+
else
|
38
|
+
raise "Cannot initialize Pixel with #{opts.inspect}."
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def inspect
|
43
|
+
"0x%0.8x" % @uint32
|
44
|
+
end
|
45
|
+
|
46
|
+
def ==(value)
|
47
|
+
case value
|
48
|
+
when self.class
|
49
|
+
@uint32 == value.uint32
|
50
|
+
when Fixnum
|
51
|
+
@uint32 == value
|
52
|
+
else
|
53
|
+
@uint32 == value
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_i
|
58
|
+
uint32
|
59
|
+
end
|
60
|
+
|
61
|
+
def r
|
62
|
+
@uint32 & 0xff000000
|
63
|
+
end
|
64
|
+
|
65
|
+
def g
|
66
|
+
@uint32 & 0x00ff0000
|
67
|
+
end
|
68
|
+
|
69
|
+
def b
|
70
|
+
@uint32 & 0x0000ff00
|
71
|
+
end
|
72
|
+
|
73
|
+
def a
|
74
|
+
@uint32 & 0x000000ff
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_rgb
|
78
|
+
{r: r, g: g, b: b}
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_rgba
|
82
|
+
{r: r, g: g, b: b, a: a}
|
83
|
+
end
|
84
|
+
|
85
|
+
def to_s
|
86
|
+
"#{ANSI.bg(*ANSI.uint32_to_rgb(@uint32))} "
|
87
|
+
end
|
88
|
+
|
89
|
+
def width
|
90
|
+
2
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class PixelHiColor < Pixel
|
95
|
+
def to_s
|
96
|
+
"#{ANSI.bg(*ANSI.uint32_to_rgb(@uint32))} "
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class Pixel2x < Pixel
|
101
|
+
|
102
|
+
def self.load_strategy(pixels, height:, width:)
|
103
|
+
matrix = Matrix.build(height, width) do |row, col|
|
104
|
+
pixels[col + row*width]
|
105
|
+
end
|
106
|
+
|
107
|
+
(0..height).step(2) do |row|
|
108
|
+
matrix.minor(row, 2, 0, width).transpose.to_a.each do |top, bottom|
|
109
|
+
yield self.new(top.to_i, bottom.to_i)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def initialize(top, bottom)
|
115
|
+
@top, @bot = top, bottom
|
116
|
+
|
117
|
+
@top ||= 0x000000ff
|
118
|
+
@bot ||= 0x000000ff
|
119
|
+
end
|
120
|
+
|
121
|
+
def width
|
122
|
+
1
|
123
|
+
end
|
124
|
+
|
125
|
+
def to_s
|
126
|
+
if ANSI.clamped(@top) == ANSI.clamped(@bot)
|
127
|
+
return "#{ANSI.bg(*ANSI.uint32_to_rgb(@top))} "
|
128
|
+
end
|
129
|
+
|
130
|
+
"#{ANSI.fg(*ANSI.uint32_to_rgb(@bot))}#{ANSI.bg(*ANSI.uint32_to_rgb(@top))}▄"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
data/lib/branding/png.rb
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
module Branding
|
2
|
+
##
|
3
|
+
# This is a simple PNG decoder, inspired and adapted from ChunkyPNG
|
4
|
+
class PNG
|
5
|
+
SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10].pack('C8').force_encoding('BINARY')
|
6
|
+
|
7
|
+
attr_reader :width, :height, :depth, :color, :compression, :filtering, :interlace
|
8
|
+
|
9
|
+
##
|
10
|
+
# Private class for holding and deflating image data
|
11
|
+
#
|
12
|
+
class Imagedata
|
13
|
+
include Enumerable
|
14
|
+
|
15
|
+
def initialize(data:, scanline_width:, color_channels: )
|
16
|
+
@scanline_width = scanline_width
|
17
|
+
@color_channels = color_channels
|
18
|
+
|
19
|
+
zstream = Zlib::Inflate.new
|
20
|
+
zstream << data
|
21
|
+
@inflated = zstream.finish
|
22
|
+
zstream.close
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# yields byte
|
27
|
+
def each
|
28
|
+
@inflated.each_byte do |byte|
|
29
|
+
yield byte
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def each_scanline
|
34
|
+
bytes_in_scanline = @scanline_width * @color_channels + 1 #the number of bytes is +1 because of the filter byte
|
35
|
+
previous_scanline = nil
|
36
|
+
|
37
|
+
each_slice(bytes_in_scanline) do |scanline|
|
38
|
+
filter_bit, *rest = *scanline
|
39
|
+
filter = Filter.new(filter_bit, rest, previous_scanline, @color_channels)
|
40
|
+
|
41
|
+
recon = filter.reconstructed_scanline
|
42
|
+
yield recon
|
43
|
+
previous_scanline = recon
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def each_pixel
|
48
|
+
each_scanline do |scanline|
|
49
|
+
scanline.each_slice(@color_channels) do |*rgba|
|
50
|
+
yield Pixel.new(*rgba)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# private class to handle reconstructing filtered data
|
58
|
+
# https://www.w3.org/TR/PNG/#9Filters
|
59
|
+
class Filter
|
60
|
+
##
|
61
|
+
#
|
62
|
+
def initialize(filter_bit, filtered_scanline, previous_scanline, color_channels)
|
63
|
+
@type = filter_bit
|
64
|
+
@filtered = filtered_scanline
|
65
|
+
@previous = previous_scanline || []
|
66
|
+
@pixel_width = color_channels
|
67
|
+
@position = 0
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# yields a reconstructed byte
|
72
|
+
def reconstructed_scanline
|
73
|
+
@reconstructed_scanline = []
|
74
|
+
@filtered.each do |byte|
|
75
|
+
recon = case @type
|
76
|
+
when 0
|
77
|
+
none(byte)
|
78
|
+
when 1
|
79
|
+
sub(byte)
|
80
|
+
when 2
|
81
|
+
up(byte)
|
82
|
+
when 3
|
83
|
+
average(byte)
|
84
|
+
when 4
|
85
|
+
paeth(byte)
|
86
|
+
end
|
87
|
+
|
88
|
+
@reconstructed_scanline << (recon % 256)
|
89
|
+
@position += 1
|
90
|
+
end
|
91
|
+
|
92
|
+
@reconstructed_scanline
|
93
|
+
end
|
94
|
+
|
95
|
+
# a: the byte corresponding to x in the pixel immediately before the pixel
|
96
|
+
# containing x (or the byte immediately before x, when the bit depth is
|
97
|
+
# less than 8)
|
98
|
+
def a
|
99
|
+
offset = @position - @pixel_width
|
100
|
+
return 0x00 if offset < 0
|
101
|
+
|
102
|
+
@reconstructed_scanline[offset]
|
103
|
+
end
|
104
|
+
|
105
|
+
def b
|
106
|
+
@previous[@position] || 0x00
|
107
|
+
end
|
108
|
+
|
109
|
+
def c
|
110
|
+
offset = @position - @pixel_width
|
111
|
+
return 0x00 if offset < 0
|
112
|
+
|
113
|
+
@previous[offset]
|
114
|
+
end
|
115
|
+
|
116
|
+
def none(x)
|
117
|
+
x
|
118
|
+
end
|
119
|
+
|
120
|
+
def sub(x)
|
121
|
+
x + a
|
122
|
+
end
|
123
|
+
|
124
|
+
def up(x)
|
125
|
+
x + b
|
126
|
+
end
|
127
|
+
|
128
|
+
def average(x)
|
129
|
+
x + ((a + b) / 2.0).floor
|
130
|
+
end
|
131
|
+
|
132
|
+
# https://www.w3.org/TR/PNG/#9Filter-type-4-Paeth
|
133
|
+
def paeth(x)
|
134
|
+
x + paeth_predictor(a,b,c)
|
135
|
+
end
|
136
|
+
|
137
|
+
def paeth_predictor(a,b,c)
|
138
|
+
p = a + b - c
|
139
|
+
pa = (p - a).abs
|
140
|
+
pb = (p - b).abs
|
141
|
+
pc = (p - c).abs
|
142
|
+
|
143
|
+
return a if pa <= pb && pa <= pc
|
144
|
+
|
145
|
+
return b if pb <= pc
|
146
|
+
|
147
|
+
c
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.from_file(path)
|
152
|
+
new(File.open(path, 'rb'))
|
153
|
+
end
|
154
|
+
|
155
|
+
def initialize(io)
|
156
|
+
signature = io.read(SIGNATURE.length)
|
157
|
+
raise 'Signature mismatch' unless signature == SIGNATURE
|
158
|
+
|
159
|
+
@data = ''
|
160
|
+
|
161
|
+
until io.eof?
|
162
|
+
type, content = read_chunk(io)
|
163
|
+
|
164
|
+
case type
|
165
|
+
when 'IHDR'
|
166
|
+
fields = content.unpack('NNC5')
|
167
|
+
@width, @height, @depth, @color, @compression, @filtering, @interlace = fields
|
168
|
+
|
169
|
+
when 'IDAT'
|
170
|
+
@data << content
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
unless depth == 8
|
175
|
+
raise NotImplementedError, 'only supporting 8bit color depth'
|
176
|
+
end
|
177
|
+
|
178
|
+
unless color == 2 || color == 6
|
179
|
+
raise NotImplementedError, 'only supporting true color, with or without alpha'
|
180
|
+
end
|
181
|
+
|
182
|
+
unless filtering == 0
|
183
|
+
raise NotImplementedError, 'does not supporting filtering'
|
184
|
+
end
|
185
|
+
|
186
|
+
unless compression == 0
|
187
|
+
raise NotImplementedError, 'only supporting deflate compression'
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def pixels
|
192
|
+
if block_given?
|
193
|
+
imagedata.each_pixel
|
194
|
+
else
|
195
|
+
imagedata.enum_for(:each_pixel).to_a
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
##
|
200
|
+
# the number of color channels. Not the PNG "color mode"
|
201
|
+
def color_channels
|
202
|
+
color == 2 ? 3 : 4
|
203
|
+
end
|
204
|
+
|
205
|
+
private
|
206
|
+
|
207
|
+
def imagedata
|
208
|
+
Imagedata.new(data: @data, scanline_width: width, color_channels: color_channels)
|
209
|
+
end
|
210
|
+
|
211
|
+
def read_chunk(io)
|
212
|
+
length, type = read_bytes(io, 8).unpack('Na4')
|
213
|
+
|
214
|
+
content = read_bytes(io, length)
|
215
|
+
crc = read_bytes(io, 4).unpack('N').first
|
216
|
+
verify_crc(type, content, crc)
|
217
|
+
|
218
|
+
[type, content]
|
219
|
+
end
|
220
|
+
|
221
|
+
def read_bytes(io, length)
|
222
|
+
data = io.read(length)
|
223
|
+
|
224
|
+
if data.nil? || data.bytesize != length
|
225
|
+
raise "Could not read #{length} bytes from io"
|
226
|
+
end
|
227
|
+
|
228
|
+
data
|
229
|
+
end
|
230
|
+
|
231
|
+
def verify_crc(type, content, found_crc)
|
232
|
+
expected_crc = Zlib.crc32(content, Zlib.crc32(type))
|
233
|
+
raise 'Chuck CRC mismatch!' if found_crc != expected_crc
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Branding
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
|
4
|
+
unless Rails.env.production?
|
5
|
+
initializer('branding.print_logo') do
|
6
|
+
logo = Branding::Logo.new(Branding::Railtie.favicons)
|
7
|
+
logo.print
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
##
|
12
|
+
# find the best suited icon in a rails app
|
13
|
+
def self.favicons
|
14
|
+
paths = ["#{Rails.root}/public/", "#{Rails.root}/app/assets/images/"]
|
15
|
+
file_patterns = ['favicon*.png', 'apple-touch-icon*.png']
|
16
|
+
patterns = paths.product(file_patterns).map(&:join)
|
17
|
+
|
18
|
+
matches = Dir.glob(patterns)
|
19
|
+
|
20
|
+
matches.sort_by{|path| File.stat(path).size }.first
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/spec/pixel_spec.rb
ADDED
data/spec/png_spec.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Branding::PNG do
|
4
|
+
it 'returns a pixel matrix' do
|
5
|
+
png = Branding::PNG.from_file(fixture_file('test3x3.png'))
|
6
|
+
expect(png.pixels).to eq(
|
7
|
+
[
|
8
|
+
[0x111111, 0xc6c6c6, 0xffffff],
|
9
|
+
[0xc6c6c6, 0x111111, 0xc6c6c6],
|
10
|
+
[0xffffff, 0xc6c6c6, 0x111111]
|
11
|
+
]
|
12
|
+
)
|
13
|
+
end
|
14
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: branding
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stefan Natchev
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-06-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pry
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.10'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.10'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '11.1'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '11.1'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.4'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.4'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.40'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.40'
|
83
|
+
description: Proud of your work? Add some branding bling with Branding.rb
|
84
|
+
email:
|
85
|
+
- stefan.natchev@gmail.com
|
86
|
+
executables:
|
87
|
+
- branding
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- ".rspec"
|
93
|
+
- ".travis.yml"
|
94
|
+
- Gemfile
|
95
|
+
- LICENSE.txt
|
96
|
+
- README.md
|
97
|
+
- Rakefile
|
98
|
+
- bin/branding
|
99
|
+
- branding.gemspec
|
100
|
+
- lib/branding.rb
|
101
|
+
- lib/branding/ansi.rb
|
102
|
+
- lib/branding/canvas.rb
|
103
|
+
- lib/branding/cli.rb
|
104
|
+
- lib/branding/logo.rb
|
105
|
+
- lib/branding/pixel.rb
|
106
|
+
- lib/branding/png.rb
|
107
|
+
- lib/branding/railtie.rb
|
108
|
+
- lib/branding/version.rb
|
109
|
+
- spec/branding_spec.rb
|
110
|
+
- spec/pixel_spec.rb
|
111
|
+
- spec/png_spec.rb
|
112
|
+
- spec/spec_helper.rb
|
113
|
+
homepage: https://github.com/snatchev/branding.rb
|
114
|
+
licenses:
|
115
|
+
- MIT
|
116
|
+
metadata: {}
|
117
|
+
post_install_message:
|
118
|
+
rdoc_options: []
|
119
|
+
require_paths:
|
120
|
+
- lib
|
121
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
requirements: []
|
132
|
+
rubyforge_project:
|
133
|
+
rubygems_version: 2.4.5.1
|
134
|
+
signing_key:
|
135
|
+
specification_version: 4
|
136
|
+
summary: Print your logo to the terminal.
|
137
|
+
test_files:
|
138
|
+
- spec/branding_spec.rb
|
139
|
+
- spec/pixel_spec.rb
|
140
|
+
- spec/png_spec.rb
|
141
|
+
- spec/spec_helper.rb
|
142
|
+
has_rdoc:
|