Class: ZPNG::CLI

Inherits:
Object
  • Object
show all
Includes:
Hexdump
Defined in:
lib/zpng/cli.rb

Constant Summary collapse

DEFAULT_ACTIONS =
%w'info metadata chunks'

Instance Method Summary collapse

Methods included from Hexdump

dump, #hexdump

Constructor Details

#initialize(argv = ARGV) ⇒ CLI

Returns a new instance of CLI.



11
12
13
14
15
16
17
18
19
20
21
# File 'lib/zpng/cli.rb', line 11

def initialize argv = ARGV
  # hack #1: allow --chunk as well as --chunks
  @argv = argv.map{ |x| x.sub(/^--chunks?/,'--chunk(s)') }

  # hack #2: allow --chunk(s) followed by a non-number, like "zpng --chunks fname.png"
  @argv.each_cons(2) do |a,b|
    if a == "--chunk(s)" && b !~ /^\d+$/
      a<<"=-1"
    end
  end
end

Instance Method Details

#_conditional_hexdump(data, v2 = 2) ⇒ Object



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/zpng/cli.rb', line 176

def _conditional_hexdump data, v2 = 2
  if @options[:verbose] <= 0
    # do nothing
  elsif @options[:verbose] < v2
    sz = 0x20
    print Hexdump.dump(data[0,sz],
                      :show_offset => false,
                      :tail => data.size > sz ? " + #{data.size-sz} bytes\n" : "\n"
                     ){ |row| row.insert(0,"    ") }.gray
    puts

  elsif @options[:verbose] >= v2
    print Hexdump.dump(data){ |row| row.insert(0,"    ") }.gray
    puts
  end
end

#ansiObject



227
228
229
230
231
232
233
234
235
# File 'lib/zpng/cli.rb', line 227

def ansi
  spc = @options[:wide] ? "  " : " "
  @img.height.times do |y|
    @img.width.times do |x|
      print spc.background(@img[x,y].to_ansi)
    end
    puts
  end
end

#ansi256Object



237
238
239
240
241
242
243
244
245
246
# File 'lib/zpng/cli.rb', line 237

def ansi256
  require 'rainbow'
  spc = @options[:wide] ? "  " : " "
  @img.height.times do |y|
    @img.width.times do |x|
      print spc.background(@img[x,y].to_html)
    end
    puts
  end
end

#asciiObject



216
217
218
219
220
221
222
223
224
225
# File 'lib/zpng/cli.rb', line 216

def ascii
  @img.height.times do |y|
    @img.width.times do |x|
      c = @img[x,y].to_ascii
      c *= 2 if @options[:wide]
      print c
    end
    puts
  end
end

#chunks(idx = nil) ⇒ Object



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/zpng/cli.rb', line 193

def chunks idx=nil
  max_type_len = 0
  unless idx
    max_type_len = @img.chunks.map{ |x| x.type.to_s.size }.max
  end

  @img.chunks.each do |chunk|
    next if idx && chunk.idx != idx
    colored_type = chunk.type.ljust(max_type_len).magenta
    colored_crc =
      if chunk.crc == :no_crc # hack for BMP chunks (they have no CRC)
        ''
      elsif chunk.crc_ok?
        'CRC OK'.green
      else
        'CRC ERROR'.red
      end
    puts "[.] #{chunk.inspect(@options[:verbose]).sub(chunk.type, colored_type)} #{colored_crc}"

    _conditional_hexdump(chunk.data) unless chunk.size == 0
  end
end

#colorsObject



279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/zpng/cli.rb', line 279

def colors
  h=Hash.new(0)
  h2=Hash.new{ |k,v| k[v] = [] }
  @img.each_pixel do |c,x,y|
    h[c] += 1
    if h[c] < 6
      h2[c] << [x,y]
    end
  end

  xlen = @img.width.to_s.size
  ylen = @img.height.to_s.size

  h.sort_by{ |c,n| [n] + h2[c].first.reverse }.each do |c,n|
    printf "%6d : %s : ", n, c.inspect
    h2[c].each_with_index do |a,idx|
      print ";" if idx > 0
      if idx >= 4
        print " ..."
        break
      end
      printf " %*d,%*d", xlen, a[0], ylen, a[1]
    end
    puts
  end
end

#consoleObject



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/zpng/cli.rb', line 306

def console
  ARGV.clear # clear ARGV so IRB is not confused
  require 'irb'
  m0 = IRB.method(:setup)
  img = @img

  # override IRB.setup, called from IRB.start
  IRB.define_singleton_method :setup do |*args|
    m0.call *args
    conf[:IRB_RC] = Proc.new do |context|
      context.main.instance_variable_set '@img', img
      context.main.define_singleton_method(:img){ @img }
    end
  end

  puts "[.] img = ZPNG::Image.load(#{@fname.inspect})".gray
  IRB.start
end

#crop(geometry) ⇒ Object



127
128
129
130
131
132
133
134
# File 'lib/zpng/cli.rb', line 127

def crop geometry
  unless geometry =~ /\A(\d+)x(\d+)\+(\d+)\+(\d+)\Z/i
    STDERR.puts "[!] invalid geometry #{geometry.inspect}, must be WxH+X+Y, like 100x100+10+10"
    exit 1
  end
  @img.crop! :width => $1.to_i, :height => $2.to_i, :x => $3.to_i, :y => $4.to_i
  print @img.export unless @actions.include?(:ascii)
end

#extract_chunk(id) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/zpng/cli.rb', line 110

def extract_chunk id
  @img.chunks.each do |chunk|
    if chunk.idx == id
      case chunk
      when Chunk::ZTXT
        print chunk.text
      else
        print chunk.data
      end
    end
  end
end

#infoObject



166
167
168
169
170
171
172
173
174
# File 'lib/zpng/cli.rb', line 166

def info
  color = %w'COLOR_GRAYSCALE COLOR_RGB COLOR_INDEXED COLOR_GRAY_ALPHA COLOR_RGBA'.find do |k|
    @img.hdr.color == ZPNG.const_get(k)
  end
  puts "[.] image size #{@img.width || '?'}x#{@img.height || '?'}, #{@img.bpp}bpp, #{color}"
  puts "[.] palette = #{@img.palette}" if @img.palette
  puts "[.] uncompressed imagedata size = #{@img.imagedata_size} bytes"
  _conditional_hexdump(@img.imagedata, 3) if @options[:verbose] > 0
end

#load_file(fname) ⇒ Object



136
137
138
# File 'lib/zpng/cli.rb', line 136

def load_file fname
  @img = Image.load fname, :verbose => @options[:verbose]+1
end

#metadataObject



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/zpng/cli.rb', line 140

def 
  return if @img..empty?
  puts "[.] metadata:"
  @img..each do |k,v,h|
    if @options[:verbose] < 2
      if k.size > 512
        puts "[?] key too long (#{k.size}), truncated to 512 chars".yellow
        k = k[0,512] + "..."
      end
      if v.size > 512
        puts "[?] value too long (#{v.size}), truncated to 512 chars".yellow
        v = v[0,512] + "..."
      end
    end
    if h.keys.sort == [:keyword, :text]
      v.gsub!(/[\n\r]+/, "\n"+" "*19)
      printf "    %-12s : %s\n", k, v.gray
    else
      printf "    %s (%s: %s):", k, h[:language], h[:translated_keyword]
      v.gsub!(/[\n\r]+/, "\n"+" "*19)
      printf "\n%s%s\n", " "*19, v.gray
    end
  end
  puts
end

#paletteObject



264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/zpng/cli.rb', line 264

def palette
  if @img.palette
    pp @img.palette
    if @img.format == :bmp
      hexdump(@img.palette.data, :width => 4, :show_offset => false) do |row, offset|
        row.insert(0,"  color %4s:  " % "##{(offset/4)}")
      end
    else
      hexdump(@img.palette.data, :width => 3, :show_offset => false) do |row, offset|
        row.insert(0,"  color %4s:  " % "##{(offset/3)}")
      end
    end
  end
end

#runObject



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/zpng/cli.rb', line 23

def run
  @actions = []
  @options = { :verbose => 0 }
  optparser = OptionParser.new do |opts|
    opts.banner = "Usage: zpng [options] filename.png"
    opts.separator ""

    opts.on("-i", "--info", "General image info (default)"){ @actions << :info }
    opts.on("-c", "--chunk(s) [ID]", Integer, "Show chunks (default) or single chunk by its #") do |id|
      id = nil if id == -1
      @actions << [:chunks, id]
    end
    opts.on("-m", "--metadata", "Show image metadata, if any (default)"){ @actions << :metadata }

    opts.separator ""
    opts.on("-S", "--scanlines", "Show scanlines info"){ @actions << :scanlines }
    opts.on("-P", "--palette", "Show palette"){ @actions << :palette }
    opts.on(      "--colors", "Show colors used"){ @actions << :colors }

    opts.on "-E", "--extract-chunk ID", Integer, "extract a single chunk" do |id|
      @actions << [:extract_chunk, id]
    end
    opts.on "-D", "--imagedata", "dump unpacked Image Data (IDAT) chunk(s) to stdout" do
      @actions << :unpack_imagedata
    end

    opts.separator ""
    opts.on "-C", "--crop GEOMETRY", "crop image, {WIDTH}x{HEIGHT}+{X}+{Y},",
    "puts results on stdout unless --ascii given" do |x|
      @actions << [:crop, x]
    end

    opts.separator ""
    opts.on "-A", '--ascii', 'Try to convert image to ASCII (works best with monochrome images)' do
      @actions << :ascii
    end
    opts.on "-N", '--ansi', 'Try to display image as ANSI colored text' do
      @actions << :ansi
    end
    opts.on "-2", '--256', 'Try to display image as 256-colored text' do
      @actions << :ansi256
    end
    opts.on "-W", '--wide', 'Use 2 horizontal characters per one pixel' do
      @options[:wide] = true
    end

    opts.separator ""
    opts.on "-v", "--verbose", "Run verbosely (can be used multiple times)" do |v|
      @options[:verbose] += 1
    end
    opts.on "-q", "--quiet", "Silent any warnings (can be used multiple times)" do |v|
      @options[:verbose] -= 1
    end
    opts.on "-I", "--console", "opens IRB console with specified image loaded" do |v|
      @actions << :console
    end
  end

  if (argv = optparser.parse(@argv)).empty?
    puts optparser.help
    return
  end

  @actions = DEFAULT_ACTIONS if @actions.empty?

  argv.each_with_index do |fname,idx|
    if argv.size > 1 && @options[:verbose] >= 0
      puts if idx > 0
      puts "[.] #{fname}".color(:green)
    end
    @fname = fname

    @zpng = load_file fname

    @actions.each do |action|
      if action.is_a?(Array)
        self.send(*action) if self.respond_to?(action.first)
      else
        self.send(action) if self.respond_to?(action)
      end
    end
  end
rescue Errno::EPIPE
  # output interrupt, f.ex. when piping output to a 'head' command
  # prevents a 'Broken pipe - <STDOUT> (Errno::EPIPE)' message
end

#scanlinesObject



248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/zpng/cli.rb', line 248

def scanlines
  @img.scanlines.each do |sl|
    p sl
    case @options[:verbose]
    when 1
      hexdump(sl.raw_data) if sl.raw_data
    when 2
      hexdump(sl.decoded_bytes)
    when 3..999
      hexdump(sl.raw_data) if sl.raw_data
      hexdump(sl.decoded_bytes)
      puts
    end
  end
end

#unpack_imagedataObject



123
124
125
# File 'lib/zpng/cli.rb', line 123

def unpack_imagedata
  print @img.imagedata
end