Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/ruby
- #
- # generate_img.rb version 0.0.99.20211020 (2021-10-20) written by cleemy desu wayo
- #
- # this is a personal work and is a PDS (Public Domain Software)
- #
- # --------
- # requirements: Ruby 2.7 or later, RMagick, ImageMagick, GNU Unifont
- # (and maybe this code works under only Linux)
- #
- # if you use Ubuntu 20.04, you should probably install ruby-dev, make, pkg-config,
- # imagemagick, and libmagickwand-dev before "sudo gem install rmagick"
- #
- # on Ubuntu, GNU Unifont becomes available by doing "sudo apt-get install unifont"
- # --------
- #
- # see https://twitter.com/cleemy4545/status/1450863408400801793 and the thread
- #
- # old version: https://pastebin.com/2pEVnM8Q
- #
- require 'rmagick'
- if !File.readable?(ARGV[0].to_s) && ARGV[0] != '--stdin'
- warn 'please specify a valid file name (or specify "--stdin")'
- exit 1
- end
- result_img = nil
- dir_img = './'
- dir_textimg = './'
- is_dir_textimg_overridden = false
- pos = { x: 0, y: 0 }
- margin = { x: 0, y: 0 }
- step = { x: 1, y: 0 }
- text_size = 16
- text_color = [0, 0, 0]
- text_font = 'unifont'
- text_step = 1
- def paste_img(result_img, params)
- img_piece = Magick::Image.read(params[:src]).first
- result_img.composite!(img_piece, params[:x], params[:y], Magick::OverCompositeOp)
- [img_piece.columns, img_piece.rows]
- end
- #
- # parse_args(source_str): parse source_str and return an Array
- #
- # parse_args('(100,100,20,#ffffff)') # => ["100", "100", "20", "#ffffff"]
- # parse_args('(100)') # => ["100"]
- # parse_args(' (100) ') # => ["100"]
- # parse_args('( 100 , 100 )') # => ["100", "100"]
- # parse_args('( 100 , 10 0 )') # => ["100", "10 0"]
- # parse_args('(,,20)') # => ["", "", "20"]
- # parse_args('(20,,)') # => ["20"]
- # parse_args('(,,,)') # => []
- # parse_args('( , , , )') # => ["", "", "", ""]
- # parse_args('()') # => []
- # parse_args('( )') # => [""]
- # parse_args('') # => []
- # parse_args('(())') # => ["()"]
- # parse_args('(100,100') || "invalid string" # => []
- # parse_args([100, 100]) || "not string" # => []
- #
- def parse_args(source_str)
- return [] unless source_str.is_a?(String)
- args_str = source_str.strip.sub(/^\((.*)\)$/, '\1') # ' (100,100) ' --> '100,100'
- return [] if args_str == source_str
- args_str.split(',').map(&:strip)
- end
- #
- # proper_file_path(file_path, base_file_path_src): return a proper file path
- #
- # this ignores base_file_path_src if file_path starts with '/', 'file:///',
- # 'https://', 'ftp://' or some such
- #
- def proper_file_path(file_path, base_file_path_src = '')
- # has a control code?
- return '' if "#{file_path}#{base_file_path_src}".match?(/[[:cntrl:]]/)
- result_str = ''
- if file_path.start_with?('/', 'file:///')
- result_str = file_path.sub(/\A(file:)?\/+/, '/')
- elsif file_path.match?(/^[a-zA-Z]+:\/\//)
- result_str = file_path.sub(/\A([a-zA-Z]+):\/\/+/, '\1://')
- else
- base_file_path = base_file_path_src.to_s
- if base_file_path == ''
- base_file_path = '.'
- elsif base_file_path.start_with?('/', 'file:///')
- base_file_path = base_file_path.sub(/\A(file:)?\/+/, '/')
- elsif base_file_path.match?(/^[a-zA-Z]+:\/\//)
- base_file_path = base_file_path.sub(/\A([a-zA-Z]+):\/\/+/, '\1://')
- else
- base_file_path = './' + base_file_path.sub(/^(\.\/+)+/, '')
- end
- result_str = base_file_path.sub(/\/+\z/, '') + '/' + file_path.sub(/\A(\.\/+)+/, '')
- end
- file_prefix_allow_list = ['./', '/']
- return '' unless result_str.start_with?(*file_prefix_allow_list)
- result_str
- end
- if ARGV[0] == '--stdin'
- lines = $stdin.readlines
- else
- lines = File.foreach(ARGV[0])
- end
- lines.each do |line|
- new_img_piece_size = { x: 0, y: 0 }
- case line.chomp
- when /\Adir *: *([-_a-zA-Z0-9.\/%:]+) *\z/
- dir_img = proper_file_path($1)
- dir_textimg = dir_img unless is_dir_textimg_overridden
- when /\Adir *\( *textimg *\) *: *([-_a-zA-Z0-9.\/%:]+) *\z/
- dir_textimg = proper_file_path($1)
- is_dir_textimg_overridden = true
- when /\Abg *: *([-_a-zA-Z0-9.\/%:]+) *\z/
- bg_src = proper_file_path($1, dir_img)
- next unless File.readable?(bg_src)
- result_img = Magick::Image.read(bg_src).first
- when /\Apaste *(\(.*?\))? *: *([-_a-zA-Z0-9.\/%:]+) *\z/
- next unless result_img
- file_path = proper_file_path($2, dir_img)
- next unless File.readable?(file_path)
- paste_params = { src: file_path,
- x: pos[:x],
- y: pos[:y] }
- # "some_array.then { _1 unless _1.empty? }&.then" works like "some_array.presence&.then"
- parse_args($1).then { _1 unless _1.empty? }&.then do |args|
- paste_params[:x] += args[0].to_f if args[0]&.match?(/\A-?[0-9]+(\.[0-9]+)?\z/)
- paste_params[:y] += args[1].to_f if args[1]&.match?(/\A-?[0-9]+(\.[0-9]+)?\z/)
- end
- new_img_piece_size[:x], new_img_piece_size[:y] = paste_img(result_img, paste_params)
- %i[x y].each { pos[_1] += new_img_piece_size[_1] * step[_1] + margin[_1] }
- when /\Atextimg *(\(.*?\))? *: *(.+)\z/
- next unless result_img
- xpos_buff = pos[:x] # "textimg:" does not change pos[:x]
- ypos_buff = pos[:y] # "textimg:" does not change pos[:y]
- # "some_array.then { _1 unless _1.empty? }&.then" works like "some_array.presence&.then"
- parse_args($1).then { _1 unless _1.empty? }&.then do |args|
- xpos_buff += args[0].to_f if args[0]&.match?(/\A-?[0-9]+(\.[0-9]+)?\z/)
- ypos_buff += args[1].to_f if args[1]&.match?(/\A-?[0-9]+(\.[0-9]+)?\z/)
- end
- $2.each_char do |c|
- file_path = proper_file_path("textimg_#{c.ord.to_s(16)}.png", dir_textimg)
- next unless File.readable?(file_path)
- paste_params = { src: file_path,
- x: xpos_buff,
- y: ypos_buff }
- new_img_piece_size[:x], new_img_piece_size[:y] = paste_img(result_img, paste_params)
- xpos_buff += new_img_piece_size[:x] # "textimg:" does not change pos[:x]
- end
- when /\Atext *(\(.*?\))? *: *(.+)\z/
- next unless result_img
- color_str = format('#%<r>02x%<g>02x%<b>02x', r: text_color[0], g: text_color[1], b: text_color[2])
- text_params = { body: $2,
- x: pos[:x],
- y: pos[:y],
- size: text_size.to_i,
- color: color_str }
- # "some_array.then { _1 unless _1.empty? }&.then" works like "some_array.presence&.then"
- parse_args($1).then { _1 unless _1.empty? }&.then do |args|
- text_params[:x] += args[0].to_f if args[0]&.match?(/\A-?[0-9]+(\.[0-9]+)?\z/)
- text_params[:y] += args[1].to_f if args[1]&.match?(/\A-?[0-9]+(\.[0-9]+)?\z/)
- text_params[:size] = args[2].to_i if args[2]&.match?(/\A[0-9]+(\.[0-9]+)?\z/)
- text_params[:color] = args[3] if args[3]&.match?(/\A#[0-9a-f]{6}\z/)
- end
- Magick::Draw.new.annotate(result_img, 0, 0,
- text_params[:x],
- text_params[:y] + text_params[:size],
- text_params[:body]) do
- _1.font = text_font
- _1.fill = text_params[:color]
- _1.pointsize = text_params[:size].to_i
- end
- pos[:y] += text_size * text_step + margin[:y]
- when /\Atext_([a-z]+) *: *(.+?) *\z/
- case $1
- when 'font'
- text_font = $2
- when 'size'
- text_size = $2.to_f
- when 'color'
- if $2.start_with?('#')
- regex = /\A#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})\z/
- radix = 16
- else
- regex = /\A([0-9]+) *, *([0-9]+) *, *([0-9]+)\z/
- radix = 10
- end
- $2.match(regex)&.then do |matched|
- text_color = matched[1..3].map { _1.to_i(radix) }
- end
- when 'step'
- text_step = $2.to_f
- end
- when /\A(blank|margin|pos|step) *(\(.*?\))? *: *([-0-9.]+?) *\z/
- args = parse_args($2)
- next unless ['x', 'y', '', nil].include?(args[0])
- axes = %i[x y]
- case args[0]
- when 'x' then axes = [:x]
- when 'y' then axes = [:y]
- end
- axes.each do |axis|
- case $1
- when 'blank' then pos[axis] += $3.to_f
- when 'margin' then margin[axis] = $3.to_f
- when 'pos' then pos[axis] = $3.to_f
- when 'step' then step[axis] = $3.to_f
- end
- end
- end
- end
- unless result_img
- warn 'error: no background image'
- exit 2
- end
- if ARGV[1] == '--show'
- result_img.display
- exit
- end
- result_img.write('generated.png')
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement