pixel_raster 0.0.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c746ea24abf78533158d4767d3f034f4c4b37ab1
4
- data.tar.gz: 30ada28f6043068d7338a8253f36e4230203b680
2
+ SHA256:
3
+ metadata.gz: dd6246ac2fb2fc015e61ec280b5be406f2eade547be1dce4ced45dbd6120a208
4
+ data.tar.gz: 5a2adb8d02a378a963b52304556e6d5636625da5fb4599695c6bf7167c06b0d9
5
5
  SHA512:
6
- metadata.gz: d306d952c1dc81df878e1e2f2ccca329db410c8d371d9445371f95e164fa1c0ad1d4890501c84a5ec2df42cd87197c6e2c0f1d97f64bb3c474c35da4afe65a75
7
- data.tar.gz: 366517b49d2af18e08ea294eb84a89207aac6fceec89a7fc01ff70147c22dbf6b36f7b2842f0a63f7c3bf9a7244423bbf48ac5630385638c41aa7848e0d9aa54
6
+ metadata.gz: 9665873f9a4bfa9525f02a17bd0e2a910a07e26304e90d7ff7580be387a871f197ffa553eaecba4021a299c296264100dad4f59d0e9fd399568b700c07b0d6ae
7
+ data.tar.gz: c3f8038be85df22b0583f2ee63da7cb7589174ee2ae44506fdbefe47b51c7d76aaf68d5ef3d4e47b60f8803cd9f6a2d519b4452929ec6469aec1bf2b3a623453
data/README.md CHANGED
@@ -21,6 +21,6 @@ paint-yourself raster versions (see [images.pdf](images.pdf)).
21
21
  ## xpm2tikz
22
22
 
23
23
  The script takes xpm images from the command line as input and
24
- outputs [tikz](https://ctan.org/pkg/pgf)-code to stdout.
24
+ outputs [tikz](https://ctan.org/pkg/pgf) code to stdout.
25
25
 
26
26
  See `xpm2tikz -h`.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
data/bin/img2pixel ADDED
@@ -0,0 +1,6 @@
1
+ #! /usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+
4
+ require 'img2pixel'
5
+
6
+ PixelRaster::MagickConverter::command_line
data/lib/img2pixel.rb ADDED
@@ -0,0 +1,154 @@
1
+ #! /usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+
4
+ require 'pixel_raster'
5
+ require 'optparse'
6
+
7
+ # Base module
8
+ module PixelRaster
9
+ class MagickConverter
10
+
11
+ # Parse command line arguments (+ARGV+) and execute accordingly
12
+ #
13
+ # See +img2pixel -h+ for a list of options.
14
+ def self.command_line(argv=ARGV, stdout=$stdout, stderr=$stderr)
15
+ outfile = nil
16
+ outdir = nil
17
+ outmode = :both
18
+ empty_suffix = '-empty'
19
+ filled_suffix = '-filled'
20
+ params = {
21
+ type: :svg,
22
+ bg_color: 'white'
23
+ }
24
+
25
+ if argv.length == 0 # no argument given
26
+ argv << '-h' # give the help text
27
+ end
28
+
29
+ begin
30
+ OptionParser.new do |p|
31
+ p.accept(Symbol) { |s| s.downcase.to_sym }
32
+
33
+ p.banner = "Convert image(s) to a pixel representation."
34
+
35
+ p.separator ""
36
+ p.separator "Usage: #{$0} [options] INFILE [INFILE…]"
37
+ p.separator ""
38
+ p.separator "For each INFILE two output files are written for the empty and filled version"
39
+ p.separator "(but see --mode)."
40
+ p.separator ""
41
+ p.separator "With “-O OUTFILE” all images are output to OUTFILE."
42
+ p.separator "Caveat: having several <svg>-elements concatenated in one file"
43
+ p.separator "may not be supported by your viewer."
44
+ p.separator ""
45
+ p.separator "Options:"
46
+ p.on_tail("-h", "--help", "Show this help message and exit.") do
47
+ stdout.puts p
48
+ exit
49
+ end
50
+
51
+ p.on("-V", "--version", "Show version information and exit.") do
52
+ stdout.puts File.read(File.join(File.dirname(__FILE__),'..','VERSION'))
53
+ exit
54
+ end
55
+
56
+ p.on('-O', '--output FILENAME',
57
+ 'Write output to FILENAME (“-” for STDOUT).',
58
+ 'If this option is given, all images',
59
+ 'are written to the same file.',
60
+ 'If this is given, “-d” is ignored.') do |filename|
61
+ outfile = filename
62
+ end
63
+
64
+ p.on("-#", "--nr-of-colors N", Numeric,
65
+ "Number of colors for the final image.") do |n|
66
+ params[:nr_of_colors] = n
67
+ end
68
+
69
+ p.on('-c', '--bg-color COLOR',
70
+ "Background-color for empty version.",
71
+ "SVG color name. May be “none”.",
72
+ "Ignored for --type tikz.",
73
+ "Default: #{params[:bg_color]}") do |color|
74
+ params[:bg_color] = color
75
+ end
76
+
77
+ p.on('-t', '--type TYPE', Symbol,
78
+ 'The output type (svg or tikz).',
79
+ "Default: #{params[:type]}") do |type|
80
+ [:svg,:tikz].include?(type) or
81
+ p.abort("unknown output type #{type}")
82
+ params[:type] = type
83
+ end
84
+
85
+ p.on('-m', '--mode MODE', Symbol,
86
+ 'Output mode (both, empty, filled).',
87
+ "Default: #{outmode}") do |mode|
88
+ [:both,:empty,:filled].include?(mode) or
89
+ p.abort("unknown mode #{mode}")
90
+ outmode = mode
91
+ end
92
+
93
+ p.on('-l','--light2dark',
94
+ 'color numbering direction.',
95
+ 'Default: light2dark (unless 2 color imgs)') do
96
+ params[:light2dark] = true
97
+ end
98
+
99
+ p.on('-L','--dark2light',
100
+ 'color numbering direction.',
101
+ 'Default: light2dark (unless 2 color imgs)') do
102
+ params[:light2dark] = false
103
+ end
104
+
105
+ p.on('-d', '--dir DIR',
106
+ 'directory for output files.',
107
+ 'Must exist and be writeable.',
108
+ "Default: same as INFILE") do |dir|
109
+ outdir = dir
110
+ end
111
+ end.parse!(argv)
112
+ rescue OptionParser::ParseError => pe
113
+ stderr.puts pe
114
+ exit 22 # Errno::EINVAL::Errno (but may not exist on all platforms)
115
+ end
116
+
117
+
118
+ MagickConverter.new(**params) do |c|
119
+ if outfile == '-' # Write everything to standard out
120
+ argv.each do |infile|
121
+ stdout << c.convert(infile, mode: :empty) unless outmode==:filled
122
+ stdout << c.convert(infile, mode: :filled) unless outmode==:empty
123
+ end
124
+ elsif outfile # an outfile is given, so write everything in this file
125
+ File.open(outfile, 'w') do |out|
126
+ argv.each do |infile|
127
+ out << c.convert(infile, mode: :empty)
128
+ out << c.convert(infile, mode: :filled)
129
+ end
130
+ end
131
+ else
132
+ argv.each do |infile|
133
+ # if no outfile is given we generate the filename from the infile
134
+ base_out = File.join(outdir || File.dirname(infile),
135
+ File.basename(infile, '.*'))
136
+
137
+ empty_out = base_out + empty_suffix + '.' + params[:type].to_s
138
+ filled_out = base_out + filled_suffix + '.' + params[:type].to_s
139
+ unless outmode==:filled
140
+ File.open(empty_out,"w") do |out|
141
+ out << c.convert(infile, mode: :empty)
142
+ end
143
+ end
144
+ unless outmode==:empty
145
+ File.open(filled_out,"w") do |out|
146
+ out << c.convert(infile, mode: :filled)
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
data/lib/pixel_raster.rb CHANGED
@@ -1,44 +1,185 @@
1
1
  #! /usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+
4
+ require 'rmagick'
2
5
 
3
6
  module PixelRaster
4
- # read xpm files
5
- # this is not very robust but assumes a straight forward file format
6
- # without color/grayscale etc being mixed.
7
- def read_xpm(filename)
8
- lines = File.readlines(filename)
9
- lines.shift # get rid of /* XPM */
10
- lines.shift # get rid of static char * ...[] = {
11
- meta = lines.shift.scan(/[0-9]+/)
12
- n_x = meta[0].to_i
13
- n_y = meta[1].to_i
14
- n_of_colors = meta[2].to_i
15
- n_of_chars = meta[3].to_i
16
-
17
- colors = {}
7
+ class MagickConverter
8
+
9
+ attr_reader :nr_of_colors, :bg_color, :resize, :light2dark, :type
10
+
11
+ # The converter is initialized with the constraints
12
+ # for the images to be processed.
13
+ #
14
+ # Any image must have a color index (palette), this is enforced.
15
+ # It is not that useful to have more than 10 colors or more than 30x30
16
+ # pixels, but this is only enforced if the corresponding parameters
17
+ # are given.
18
+ # @see #read_image
19
+ #
20
+ # @param nr_of_colors maximum number of colors for the color index
21
+ # @param bg_color background color for empty pixel image (svg)
22
+ # @param resize [String] resize string (see ImageMagick resize option)
23
+ # @param light2dark [Boolean] if true 0 is the lightest color
24
+ # @param type [:svg|:tikz]
25
+ def initialize(nr_of_colors: nil, bg_color: 'white',
26
+ resize: nil, light2dark: nil,
27
+ type: :svg)
28
+ @nr_of_colors = nr_of_colors
29
+ @bg_color = bg_color
30
+ @resize = resize
31
+ @light2dark = light2dark
32
+ @type = type
33
+ yield(self) if block_given?
34
+ end
18
35
 
19
- n_of_colors.times do
20
- lines.shift =~ /"(.{#{n_of_chars}})\s+([cgms]4?)\s+(.*?)"/
21
- colors[$1] = $3
36
+ # Reads an image using RMagick and adjusts size and palette.
37
+ # As we need an indexed image we check and create one if neccessary.
38
+ #
39
+ # @see #initialize
40
+ # @param filename read this file
41
+ # @return [Magick::Image] the (processed) image
42
+ def read_image(filename)
43
+ # We assume each file includes exactly one image
44
+ image = Magick::Image.read(filename).first
45
+
46
+ # on demand we resize the image
47
+ # the API is kinda complicated here:
48
+ # change_geometry computes the cols and rows from the given resize string
49
+ # and yields the given block with these values
50
+ if @resize
51
+ image.change_geometry(@resize) do |cols, rows, i|
52
+ image = i.resize!(cols, rows)
53
+ end
54
+ end
55
+
56
+ # on demand we reduce the number of colors
57
+ if @nr_of_colors
58
+ image = image.quantize(@nr_of_colors)
59
+ else
60
+ # If the image does not have a palette/color index (and no number of
61
+ # columns was given) we create one with 2 colors.
62
+ unless image.palette?
63
+ image = image.quantize(2)
64
+ end
65
+ end
66
+ # finally we remove duplicate colors.
67
+ image.compress_colormap!
68
+
69
+ return image
70
+ end
71
+
72
+ # Wrapper for the converter methods.
73
+ #
74
+ # @param mode [:empty|:filled] the output mode
75
+ # @param type [:svg|:tikz] the output type
76
+ # @see #image2tikz
77
+ # @see #image2svg
78
+ def convert(filename, mode:, type: @type)
79
+ img = read_image(filename)
80
+ case type
81
+ when :tikz
82
+ image2tikz(img, prefix: mode==:empty ? 'n' : 'y')
83
+ when :svg
84
+ image2svg(img, mode: mode)
85
+ else
86
+ raise "unknown output type #{type}"
87
+ end
22
88
  end
23
89
 
24
- (1..n_y).collect do
25
- lines.shift[1..n_x].scan(/.{#{n_of_chars}}/).collect { |c| colors[c] }
90
+
91
+ # create a tikz pixel representation of an image.
92
+ #
93
+ # @param img [Magick::Image] the image
94
+ # @param prefix [String] node class prefix (to distinguish empty and filled mode)
95
+ def image2tikz(img, prefix: "n")
96
+
97
+ colormap = compute_colormap(img)
98
+
99
+ tikz = " \\begin{tikzpicture}[yscale=-1]
100
+ \\draw[step=1,help lines] (0,0) grid (#{img.columns},#{img.rows});
101
+ "
102
+ img.each_pixel do |p, c, r|
103
+ cc = colormap[p]
104
+ tikz << " \\node[#{prefix}#{cc}] at (#{c+0.5},#{r+0.5}) {#{cc}};\n"
105
+ end
106
+ tikz << "
107
+ \\end{tikzpicture}
108
+
109
+ "
110
+ tikz
111
+ end
112
+
113
+ # create a svg pixel representation of an image.
114
+ #
115
+ # @param img [Magick::Image] the image
116
+ # @param mode [:empty|:filled] fill mode
117
+ def image2svg(img, mode: :empty)
118
+
119
+ colormap = compute_colormap(img)
120
+
121
+ # ToDo: make these flexible:
122
+ xw = 20
123
+ yh = 20
124
+ xwt = 10
125
+ yht = 15
126
+
127
+ # We create the SVG directly by string
128
+ svg = %Q'<svg xmlns="http://www.w3.org/2000/svg">
129
+ <style>
130
+ .pixel {
131
+ stroke : black;
132
+ stroke-width : 1;
133
+ '
134
+ if mode == :empty
135
+ svg << "
136
+ fill: #{@bg_color};
137
+ "
138
+ end
139
+ svg << '
140
+ }
141
+ '
142
+ if mode == :filled
143
+ colormap.each do |k,v|
144
+ color = k.to_color(Magick::AllCompliance,false, 8, true)
145
+ textcolor = k.to_hsla[2] > 50 ? 'black' : 'white'
146
+ svg << %Q{ .p#{v} { fill: #{color}; }\n}
147
+
148
+ svg << %Q{ .t#{v} { fill: #{textcolor}; }\n}
149
+ end
150
+ end
151
+ svg << %Q{ </style>\n}
152
+ img.each_pixel do |p, c, r|
153
+ cc = colormap[p]
154
+ svg << %Q{ <rect x="#{c*xw}" y="#{r*yh}" width="#{xw}" height="#{yh}" class="pixel p#{cc}" />\n}
155
+ svg << %Q{ <text x="#{c*xw+xwt}" y="#{r*yh+yht}" text-anchor="middle" class="t#{cc}">#{cc}</text>\n}
156
+ end
157
+ svg << "</svg>\n"
158
+ svg
159
+ end
160
+
161
+ # compute an ordered colormap for the image
162
+ # @param img [Magick::Image] the image
163
+ # @param light2dark [Boolean] whether to invert the color map
164
+ def compute_colormap(img, light2dark: @light2dark)
165
+ # we create a colormap sorted from dark to light
166
+ colormap= {};
167
+ colors = img.color_histogram.map(&:first).sort_by(&:intensity)
168
+ # at least for a black and white image having 0 as white and
169
+ # 1 as black is more intuitive as you paint the pixels in black
170
+ # which are set. This is different from the common internal
171
+ # representation of images!
172
+ # We therefore assume light2dark _unless_ it's a 2-color image
173
+ # if it is not explicitely set:
174
+ light2dark = colors.length != 2 if light2dark.nil?
175
+ colors.reverse! if light2dark
176
+ colors.each_with_index do |p,i|
177
+ colormap[p]=i
178
+ end
179
+ return colormap
26
180
  end
27
- end
28
-
29
- def xpm2tikz(filename, prefix="n")
30
- xpm = read_xpm(filename)
31
- "\\begin{tikzpicture}[yscale=-1]
32
- \\draw[step=1,help lines] (0,0) grid (#{xpm.first.length},#{xpm.length});
33
- " + xpm.each_with_index.collect { |row,r|
34
- row.each_with_index.collect { |color,c|
35
- cc = color=='#FFFFFF' ? '0' : '1'
36
- "\\node[#{prefix+cc}] at (#{c+0.5},#{r+0.5}) {#{cc}};"
37
- }.join("\n")
38
- }.join("\n") + "
39
- \\end{tikzpicture}
40
- "
41
181
  end
42
182
  end
43
183
 
184
+
44
185
 
metadata CHANGED
@@ -1,28 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pixel_raster
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Klaus Stein
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-24 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2019-08-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rmagick
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mititest-rg
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
13
41
  description: Create a pixel raster representation of an image (for educational purposes).
14
42
  email: ruby@istik.de
15
43
  executables:
16
- - xpm2tikz
44
+ - img2pixel
17
45
  extensions: []
18
46
  extra_rdoc_files: []
19
47
  files:
20
48
  - Beispiel.svg
21
49
  - LICENSE
22
50
  - README.md
23
- - bin/xpm2tikz
51
+ - VERSION
52
+ - bin/img2pixel
53
+ - lib/img2pixel.rb
24
54
  - lib/pixel_raster.rb
25
- - lib/xpm2tikz.rb
26
55
  homepage: https://github.com/Lapizistik/pixel_raster
27
56
  licenses:
28
57
  - GPL-3.0
@@ -43,8 +72,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
43
72
  version: '0'
44
73
  requirements: []
45
74
  rubyforge_project:
46
- rubygems_version: 2.5.2.1
75
+ rubygems_version: 2.7.6.2
47
76
  signing_key:
48
77
  specification_version: 4
49
- summary: Pixel image TikZ creator
78
+ summary: Pixel image raster visualization
50
79
  test_files: []
data/bin/xpm2tikz DELETED
@@ -1,8 +0,0 @@
1
- #! /usr/bin/env ruby
2
- # -*- coding: utf-8 -*-
3
-
4
- require 'xpm2tikz'
5
-
6
- include PixelRaster
7
-
8
- command_line
data/lib/xpm2tikz.rb DELETED
@@ -1,45 +0,0 @@
1
- #! /usr/bin/env ruby
2
- # -*- coding: utf-8 -*-
3
-
4
- require 'pixel_raster'
5
- require 'optparse'
6
-
7
- module PixelRaster
8
-
9
- def command_line
10
- out = $stdout
11
-
12
- OptionParser.new do |parser|
13
- parser.banner = "\nConvert xpm image(s) to tikz. \n\nUsage: $0 [-O OUTFILE] INFILE [INFILE…]"
14
-
15
- parser.on("-h", "--help", "Show this help message") do
16
- puts parser
17
- exit
18
- end
19
-
20
- parser.on('-O', '--output FILENAME',
21
- 'write output to FILENAME (default: stdout)') do |filename|
22
- begin
23
- # We open the outfile here without ensuring it will be closed.
24
- # This is ok as we need it throughout the whole script runtime
25
- # and it will be closed on exit automatically.
26
- out = File.open(filename, 'w')
27
-
28
- # We could be more specific about catching errors, but we
29
- # exit the script anyway, and this may be different on different OSes.
30
- rescue StandardError => e
31
- warn "Could not open “#{filename}” for writing."
32
- warn e
33
- exit e.errno
34
- end
35
- end
36
- end.parse!
37
-
38
- ARGV.each do |filename|
39
- out << xpm2tikz(filename)
40
- out << "\n\\vfill\n\n"
41
- out << xpm2tikz(filename,'y')
42
- out << "\n\\vfill\n\n"
43
- end
44
- end
45
- end