Advertisement
Guest User

generate_img.rb 0.0.99.20211020 (2021-10-20) by cleemy desu wayo

a guest
Oct 20th, 2021
126
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Ruby 8.70 KB | None | 0 0
  1. #!/usr/bin/ruby
  2. #
  3. # generate_img.rb version 0.0.99.20211020 (2021-10-20) written by cleemy desu wayo
  4. #
  5. # this is a personal work and is a PDS (Public Domain Software)
  6. #
  7. # --------
  8. # requirements: Ruby 2.7 or later, RMagick, ImageMagick, GNU Unifont
  9. # (and maybe this code works under only Linux)
  10. #
  11. # if you use Ubuntu 20.04, you should probably install ruby-dev, make, pkg-config,
  12. # imagemagick, and libmagickwand-dev before "sudo gem install rmagick"
  13. #
  14. # on Ubuntu, GNU Unifont becomes available by doing "sudo apt-get install unifont"
  15. # --------
  16. #
  17. # see https://twitter.com/cleemy4545/status/1450863408400801793 and the thread
  18. #
  19. # old version: https://pastebin.com/2pEVnM8Q
  20. #
  21.  
  22. require 'rmagick'
  23.  
  24. if !File.readable?(ARGV[0].to_s) && ARGV[0] != '--stdin'
  25.   warn 'please specify a valid file name (or specify "--stdin")'
  26.   exit 1
  27. end
  28.  
  29. result_img                = nil
  30. dir_img                   = './'
  31. dir_textimg               = './'
  32. is_dir_textimg_overridden = false
  33. pos            = { x: 0, y: 0 }
  34. margin         = { x: 0, y: 0 }
  35. step           = { x: 1, y: 0 }
  36. text_size      = 16
  37. text_color     = [0, 0, 0]
  38. text_font      = 'unifont'
  39. text_step      = 1
  40.  
  41. def paste_img(result_img, params)
  42.   img_piece = Magick::Image.read(params[:src]).first
  43.   result_img.composite!(img_piece, params[:x], params[:y], Magick::OverCompositeOp)
  44.  
  45.   [img_piece.columns, img_piece.rows]
  46. end
  47.  
  48. #
  49. # parse_args(source_str): parse source_str and return an Array
  50. #
  51. # parse_args('(100,100,20,#ffffff)')         # => ["100", "100", "20", "#ffffff"]
  52. # parse_args('(100)')                        # => ["100"]
  53. # parse_args('   (100)   ')                  # => ["100"]
  54. # parse_args('(   100  ,   100   )')         # => ["100", "100"]
  55. # parse_args('(   100  ,   10 0  )')         # => ["100", "10 0"]
  56. # parse_args('(,,20)')                       # => ["", "", "20"]
  57. # parse_args('(20,,)')                       # => ["20"]
  58. # parse_args('(,,,)')                        # => []
  59. # parse_args('( , , , )')                    # => ["", "", "", ""]
  60. # parse_args('()')                           # => []
  61. # parse_args('( )')                          # => [""]
  62. # parse_args('')                             # => []
  63. # parse_args('(())')                         # => ["()"]
  64. # parse_args('(100,100') || "invalid string" # => []
  65. # parse_args([100, 100]) || "not string"     # => []
  66. #
  67. def parse_args(source_str)
  68.   return [] unless source_str.is_a?(String)
  69.  
  70.   args_str = source_str.strip.sub(/^\((.*)\)$/, '\1')  # ' (100,100) ' --> '100,100'
  71.   return [] if args_str == source_str
  72.  
  73.   args_str.split(',').map(&:strip)
  74. end
  75.  
  76. #
  77. # proper_file_path(file_path, base_file_path_src): return a proper file path
  78. #
  79. # this ignores base_file_path_src if file_path starts with '/', 'file:///',
  80. # 'https://', 'ftp://' or some such
  81. #
  82. def proper_file_path(file_path, base_file_path_src = '')
  83.  
  84.   # has a control code?
  85.   return '' if "#{file_path}#{base_file_path_src}".match?(/[[:cntrl:]]/)
  86.  
  87.   result_str = ''
  88.  
  89.   if file_path.start_with?('/', 'file:///')
  90.     result_str = file_path.sub(/\A(file:)?\/+/, '/')
  91.   elsif file_path.match?(/^[a-zA-Z]+:\/\//)
  92.     result_str = file_path.sub(/\A([a-zA-Z]+):\/\/+/, '\1://')
  93.   else
  94.  
  95.     base_file_path = base_file_path_src.to_s
  96.  
  97.     if base_file_path == ''
  98.       base_file_path = '.'
  99.     elsif base_file_path.start_with?('/', 'file:///')
  100.       base_file_path = base_file_path.sub(/\A(file:)?\/+/, '/')
  101.     elsif base_file_path.match?(/^[a-zA-Z]+:\/\//)
  102.       base_file_path = base_file_path.sub(/\A([a-zA-Z]+):\/\/+/, '\1://')
  103.     else
  104.       base_file_path = './' + base_file_path.sub(/^(\.\/+)+/, '')
  105.     end
  106.  
  107.     result_str = base_file_path.sub(/\/+\z/, '') + '/' + file_path.sub(/\A(\.\/+)+/, '')
  108.   end
  109.  
  110.   file_prefix_allow_list = ['./', '/']
  111.  
  112.   return '' unless result_str.start_with?(*file_prefix_allow_list)
  113.  
  114.   result_str
  115. end
  116.  
  117. if ARGV[0] == '--stdin'
  118.   lines = $stdin.readlines
  119. else
  120.   lines = File.foreach(ARGV[0])
  121. end
  122.  
  123. lines.each do |line|
  124.   new_img_piece_size = { x: 0, y: 0 }
  125.  
  126.   case line.chomp
  127.   when /\Adir *: *([-_a-zA-Z0-9.\/%:]+) *\z/
  128.     dir_img = proper_file_path($1)
  129.     dir_textimg = dir_img unless is_dir_textimg_overridden
  130.  
  131.   when /\Adir *\( *textimg *\) *: *([-_a-zA-Z0-9.\/%:]+) *\z/
  132.     dir_textimg = proper_file_path($1)
  133.     is_dir_textimg_overridden = true
  134.  
  135.   when /\Abg *: *([-_a-zA-Z0-9.\/%:]+) *\z/
  136.     bg_src = proper_file_path($1, dir_img)
  137.     next unless File.readable?(bg_src)
  138.  
  139.     result_img = Magick::Image.read(bg_src).first
  140.  
  141.   when /\Apaste *(\(.*?\))? *: *([-_a-zA-Z0-9.\/%:]+) *\z/
  142.     next unless result_img
  143.  
  144.     file_path = proper_file_path($2, dir_img)
  145.     next unless File.readable?(file_path)
  146.  
  147.     paste_params = { src: file_path,
  148.                      x:   pos[:x],
  149.                      y:   pos[:y] }
  150.  
  151.     # "some_array.then { _1 unless _1.empty? }&.then"  works like  "some_array.presence&.then"
  152.     parse_args($1).then { _1 unless _1.empty? }&.then do |args|
  153.       paste_params[:x] += args[0].to_f if args[0]&.match?(/\A-?[0-9]+(\.[0-9]+)?\z/)
  154.       paste_params[:y] += args[1].to_f if args[1]&.match?(/\A-?[0-9]+(\.[0-9]+)?\z/)
  155.     end
  156.  
  157.     new_img_piece_size[:x], new_img_piece_size[:y] = paste_img(result_img, paste_params)
  158.  
  159.     %i[x y].each { pos[_1] += new_img_piece_size[_1] * step[_1] + margin[_1] }
  160.  
  161.   when /\Atextimg *(\(.*?\))? *: *(.+)\z/
  162.     next unless result_img
  163.  
  164.     xpos_buff = pos[:x]    # "textimg:" does not change pos[:x]
  165.     ypos_buff = pos[:y]    # "textimg:" does not change pos[:y]
  166.  
  167.     # "some_array.then { _1 unless _1.empty? }&.then"  works like  "some_array.presence&.then"
  168.     parse_args($1).then { _1 unless _1.empty? }&.then do |args|
  169.       xpos_buff += args[0].to_f if args[0]&.match?(/\A-?[0-9]+(\.[0-9]+)?\z/)
  170.       ypos_buff += args[1].to_f if args[1]&.match?(/\A-?[0-9]+(\.[0-9]+)?\z/)
  171.     end
  172.  
  173.     $2.each_char do |c|
  174.       file_path = proper_file_path("textimg_#{c.ord.to_s(16)}.png", dir_textimg)
  175.       next unless File.readable?(file_path)
  176.  
  177.       paste_params = { src: file_path,
  178.                        x:   xpos_buff,
  179.                        y:   ypos_buff }
  180.  
  181.       new_img_piece_size[:x], new_img_piece_size[:y] = paste_img(result_img, paste_params)
  182.  
  183.       xpos_buff += new_img_piece_size[:x]    # "textimg:" does not change pos[:x]
  184.     end
  185.  
  186.   when /\Atext *(\(.*?\))? *: *(.+)\z/
  187.     next unless result_img
  188.  
  189.     color_str = format('#%<r>02x%<g>02x%<b>02x', r: text_color[0], g: text_color[1], b: text_color[2])
  190.     text_params = { body:  $2,
  191.                     x:     pos[:x],
  192.                     y:     pos[:y],
  193.                     size:  text_size.to_i,
  194.                     color: color_str }
  195.  
  196.     # "some_array.then { _1 unless _1.empty? }&.then"  works like  "some_array.presence&.then"
  197.     parse_args($1).then { _1 unless _1.empty? }&.then do |args|
  198.       text_params[:x]     += args[0].to_f if args[0]&.match?(/\A-?[0-9]+(\.[0-9]+)?\z/)
  199.       text_params[:y]     += args[1].to_f if args[1]&.match?(/\A-?[0-9]+(\.[0-9]+)?\z/)
  200.       text_params[:size]   = args[2].to_i if args[2]&.match?(/\A[0-9]+(\.[0-9]+)?\z/)
  201.       text_params[:color]  = args[3]      if args[3]&.match?(/\A#[0-9a-f]{6}\z/)
  202.     end
  203.  
  204.     Magick::Draw.new.annotate(result_img, 0, 0,
  205.                               text_params[:x],
  206.                               text_params[:y] + text_params[:size],
  207.                               text_params[:body]) do
  208.       _1.font      = text_font
  209.       _1.fill      = text_params[:color]
  210.       _1.pointsize = text_params[:size].to_i
  211.     end
  212.     pos[:y] += text_size * text_step + margin[:y]
  213.  
  214.   when /\Atext_([a-z]+) *: *(.+?) *\z/
  215.     case $1
  216.     when 'font'
  217.       text_font = $2
  218.     when 'size'
  219.       text_size = $2.to_f
  220.     when 'color'
  221.       if $2.start_with?('#')
  222.         regex = /\A#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})\z/
  223.         radix = 16
  224.       else
  225.         regex = /\A([0-9]+) *, *([0-9]+) *, *([0-9]+)\z/
  226.         radix = 10
  227.       end
  228.       $2.match(regex)&.then do |matched|
  229.         text_color = matched[1..3].map { _1.to_i(radix) }
  230.       end
  231.     when 'step'
  232.       text_step = $2.to_f
  233.     end
  234.   when /\A(blank|margin|pos|step) *(\(.*?\))? *: *([-0-9.]+?) *\z/
  235.     args = parse_args($2)
  236.     next unless ['x', 'y', '', nil].include?(args[0])
  237.  
  238.     axes = %i[x y]
  239.     case args[0]
  240.     when 'x' then axes = [:x]
  241.     when 'y' then axes = [:y]
  242.     end
  243.  
  244.     axes.each do |axis|
  245.       case $1
  246.       when 'blank'  then pos[axis]    += $3.to_f
  247.       when 'margin' then margin[axis]  = $3.to_f
  248.       when 'pos'    then pos[axis]     = $3.to_f
  249.       when 'step'   then step[axis]    = $3.to_f
  250.       end
  251.     end
  252.   end
  253. end
  254.  
  255. unless result_img
  256.   warn 'error: no background image'
  257.   exit 2
  258. end
  259.  
  260. if ARGV[1] == '--show'
  261.   result_img.display
  262.   exit
  263. end
  264.  
  265. result_img.write('generated.png')
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement