pixel_raster 0.0.2 → 1.0.0

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 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