Ruby で png 画像を自力生成(その2)

前回 png 画像を生成してみた続き。
 
こんなの。

コードはこんな感じ(画像データの生成部分のみ)。前回エントリを参照。

width, height = 400, 400

raw_data = []
height.times do |h|
  ob = []
  width.times {|w| ob << [0, 0, ((w - width / 2) ** 2 + (h - height / 2) ** 2) % 256]}
  raw_data << ob
end

 
またこんなのも。

双曲線を使う。

  width.times {|w| ob << [0, ((w - width / 2) ** 2 - (h - height / 2) ** 2) % 256, 0]}

Ruby で png 画像を自力生成できるらしいですよ

このところ脳みそが腐って全然プログラミングとかできないのだが、Ruby 愛はちっとも減じていないので、他人のブログをパクってエントリを書きます。「パクって」はいい言葉じゃないですね。とにかく唐突に「png ruby」でぐぐってみましたら、Ruby コミッタのまめさんのブログがヒットしました。
だいぶ昔の記事ですね。ほほう、Rubypng 画像を生成するかあ。これはおもしろそうですね。png の生成は結構むずかしいらしいのですが、Ruby だとそんなにむずかしくないと。とりあえず、まめさんのコードをコピペ、実行してみます。赤色のグラデーションをもった画像を生成します。もともとは Ruby 1.9 用らしいですが、Ruby 2.3.3 でまったく修正なしに走りました。(Linux Mint 18.2)

コード(コピペほぼそのまま)。
gradation.rb

require "zlib"

width, height = 100, 20
depth, color_type = 8, 2

# グラデーションのベタデータ
line = (0...width).map {|x| [x * 255 / width, 0, 0] }
raw_data = [line] * height

# チャンクのバイト列生成関数
def chunk(type, data)
  [data.bytesize, type, data, Zlib.crc32(type + data)].pack("NA4A*N")
end

# ファイルシグニチャ
print "\x89PNG\r\n\x1a\n"

# ヘッダ
print chunk("IHDR", [width, height, depth, color_type, 0, 0, 0].pack("NNCCCCC"))

# 画像データ
img_data = raw_data.map {|line| ([0] + line.flatten).pack("C*") }.join
print chunk("IDAT", Zlib::Deflate.deflate(img_data))

# 終端
print chunk("IEND", "")

"zlib" ライブラリは標準添付ライブラリなので、Gem としてインストールする必要はありません。実行は

$ ruby gradation.rb > gradation.png

みたいな感じ。こうして生成された png 画像はこれだ!
 

 
小さいけれど、ちゃんと赤色のグラデーションとして生成されていますね。うーん、いいなあこれ。


ふつうのブログならこれを解説するわけだが、面倒なのでしない。というか、まめさんの説明がすごくわかりやすいので、自分のは不要なのである。といって、じつは自分はまめさんの記事を読んで密かにぐぐったので、それだけ書いておこう。

まず、標準添付ライブラリの "zlib" が話題になっているが、これって何か? これはここを見るといいですね。つまり zlib とは、データを圧縮するアルゴリズムであるそうだ。Linux では gzip コマンドというので使われているらしいが、しかしよく Windows で使われている zip ってやつとは関係ないらしい。zlib は「可逆圧縮」、つまり解凍すると完全にもとのデータが復元できるということで、じつは png 画像ファイルの圧縮もこれであり、そう、png は劣化しないのですね。これは皆んな知っているだろう。ちなみに jpg は劣化するのですね。というわけで、png 画像を作るには、メインの画像データは zlib で圧縮すればよいというわけである。そのためのライブラリなのだった。

あとひとつ。まめさんの「臼NG はみんな見たことあるはず」がわからなかった。「臼NG」である。自分は見たことがない。これはわからないということでぐぐったら、ここがヒットした。ははあ、ファイルシグニチャ*1の「\x89P」が Shift_JIS で「臼」なわけね。納得。ただし、これを LinuxRuby で実感してみようと思ったが、

require 'kconv'

puts "\x89PNG".tosjis.encode("UTF-8")     #=>臼NG

しかやり方がわからなかった。"kconv" を使うのは Ruby 2.3 っぽくないのだけれど*2。どうやるといいのかな。(つまりは、バイナリ→Shift_JISUTF-8 の変換を文字列ですること。これ、すごく時間をかけて調べたのだけれどわからなかった。もちろんマジックコメントを使えば簡単なのだが。)


自分も上のコードを少しだけ変えて、完全にランダムな色で打点した png 画像を作ってみた。コードは変更部分のみ。
random_color_png.rb

width, height = 100, 100

# グラデーションのベタデータ
raw_data = []
height.times do
  raw_data << width.times.with_object([]) do |i, ob|
    ob << [rand(256), rand(256), rand(256)]
  end
end

画像。
 


こんな具合ですね。

*1:png ファイルの先頭部分で、これは png ファイルですよという宣言。

*2:どうも最近の Ruby だと、エンコーディングをわざとまちがえて(笑)文字化けさせるということが想定されていないらしい。文字列が自分のエンコーディングの情報をもっています。で、バイナリから例えば UTF-8 へという無意味な変換ができないらしい。そういうことをやると Encoding::InvalidByteSequenceError が出ます。

paiza オンラインハッカソン vol.6 をやってみた

これも挑戦。使用言語は Ruby。どれも超簡単なので簡潔に。
 

六村リオ

問題。結果

コード。

operations = []
gets.to_i.times {operations << gets.split.map(&:to_i)}

water = coffee = 0.0

operations.each do |op, q|
  case op
  when 1 then water  += q
  when 2 then coffee += q
  when 3
    r = 1 - q / (water + coffee)
    water  *= r
    coffee *= r
  end
end

puts (coffee * 100 / (water + coffee)).to_i

 

霧島京子

問題。結果

コード。

n = gets.to_i
masu = gets.split.map(&:to_i)
deme = []
gets.to_i.times {deme << gets.to_i}

deme.each do |pos|
  memo = []
  loop do
    if pos == n - 1
      puts "Yes"
      break
    elsif memo.include?(pos) or pos < 0 or pos >= n or masu[pos].zero?
      puts "No"
      break
    else
      memo << pos
      pos += masu[pos]
    end
  end
end

 

緑川つばめ

問題。結果

コード。

n = gets.to_i

puts n + n / 10 + n % 10

 

全体的に何だかどんどん簡単になっているような…。プログラミングを始めたばかりくらいの人向けかな?


paiza オンラインハッカソン vol.5 をやってみた

使用言語は Ruby です。画像クリックで詳細が出ます。
 

手紙の暗号を解読

20171026164239
簡単。

コード。

st = gets

result = ""
0.step(st.length - 1, 2) {|i| result += st[i]}
puts result

 

会社の入社試験

20171026164741
超簡単。こんな入社試験、ある筈がないですね。

コード。

num = gets.to_i
data = []
num.times {data << gets.to_i}

7.times do |day|
  sum = 0
  (num / 7).times {|i| sum += data[i * 7 + day]}
  puts sum
end

 
ここでレナちゃんか、ミナミちゃんのいずれかを選ばないといけない!

 

ミナミを選んでみる

問題はこれ
20171026165151
これもやさしい。

コード。

x, y = gets.split.map(&:to_i)
field = []
y.times {field << gets.split.map(&:to_i)}

result = []
field.transpose.each do |line|
  ar = Array.new(y, 0)
  result << ar.fill(1, 0, line.count(1))
end
result.transpose.reverse_each {|x| puts x.join(' ')}

transpose, fill, count, reverse_each など、僕が普段あまり使わない Array の便利な組み込みメソッドを使って解きました。Ruby の表現力の高さが出ているのではないか知らん。
 

レナを選んでみる

問題はこれ
20171026165937
うーん、大して工夫していないのに…。判定は条件分岐(if文など)を使わず、ビット演算でやったのが工夫したくらい。

コード。

x, y, num = gets.split.map(&:to_i)
table, area = [], []
y  .times {table << gets.split.map(&:to_i)}
num.times {area  << gets.split.map(&:to_i)}

MaskValue = 2 ** 10 - 1
@mask = Array.new(y) {Array.new(x, 0)}

def make_mask(x1, y1, x2, y2)
  y1.upto(y2) do |y|
    x1.upto(x2) {|x| @mask[y][x] |= MaskValue}
  end
end

area.each do |ar|
  make_mask(ar[0] - 1, ar[1] - 1, ar[2] - 1, ar[3] - 1)
end

y.times do |y1|
  x.times {|x1| table[y1][x1] &= @mask[y1][x1]}
end

puts table.flatten.inject(&:+)

 
今回も特にむずかしいことはなし。



paiza オンラインハッカソン vol.4 をやってみた

またまた挑戦してみました(ヒマ人^^;)。使用言語は Ruby です。画像クリックで詳細が出ます。
 

ミッション1

20171026103404
こりゃ簡単すぎるだろう。

コード。

data = []
gets.to_i.times {data << gets.to_i}

puts data.inject(&:+)

 

ミッション2

20171026103849
これも超簡単。

コード。

data = []
gets.to_i.times {data << gets.split.map(&:to_i)}

result = 0
data.each do |d|
  a = d[0] - d[1]
  result += a * d[2] if a > 0
end
puts result

 

ミッション3

20171026104200
簡単なのだけれど、たぶんもっといいアルゴリズムがあるでしょう。自分のコードに不満。

コード。

len, koma_size = gets.split.map(&:to_i)
koma = []
koma_size.times {koma << gets.to_i}

max = point = koma[0, len].inject(&:+)
(koma_size - len).times do |i|
  point = point - koma[i] + koma[i + len]
  max = point if point > max
end
puts max

コメントで60点とか言っている人は、ループを二重にしているのだと思う。
 
全体的にこれまでの paiza オンラインハッカソンに比べて極端に簡単。これでは差がつかないのではないか。



paiza オンラインハッカソン vol.2 をやってみた

またまた Ruby でやってみました。
 

とりあえず何の工夫もないもの

誰でもすぐに考えそうな方法でやってみました。つまりは総当り。

全然ダメですね。詳しい結果はこちら

コード。

height, width = gets.split.map(&:to_i)
@window = []
height.times {@window << gets.chomp.chars.map(&:to_i)}
num = gets.to_i
wgt_size = []
num.times {wgt_size << gets.split.map(&:to_i)}

def check(wgth, wgtw, h, w)
  wgth.times do |wh|
    wgtw.times {|ww| return false unless @window[wh + h][ww + w].zero?}
  end
  true
end

num.times do |i|
  h, w = wgt_size[i]
  co = 0
  (height - h + 1).times do |h1|
    (width - w + 1).times {|w1| co += 1 if check(h, w, h1, w1)}
  end
  puts co
end

ループが 5重になっています。これではデータの量が増えるとどうしようもないですね。
 

工夫する

それぞれのマスは空いているかいないかなので、ビット演算で判定することにします。ただし、与えられたデータは空いている場所が 0 なので、ビットを反転させて処理します。

いや、まだまだ木野ちゃん喜んでくれないですね。詳しい結果はこちら

コード。

height, width = gets.split.map(&:to_i)
wd = []
height.times {wd << (~ gets.chomp.to_i(2) & ("1" * width).to_i(2))}
num = gets.to_i
wgt_size = []
num.times {wgt_size << gets.split.map(&:to_i)}

window = wd.inject {|r, i| r * 2 ** width + i}

num.times do
  h, w = wgt_size.shift
  if h > height or w > width
    puts 0
  else
    co = 0
    widget = 0
    h.times {widget = widget * 2 ** width + ("1" * w).to_i(2)}
    (height - h + 1).times do
      wgt = widget
      (width - w + 1).times do
        co += 1 if window & widget == widget
        widget = widget << 1    #ビットシフト
      end
      widget = wgt << width     #ビットシフト
    end
    puts co
  end
end

ループは 3重です。
 

最終形

もう少し工夫します。配置可能位置がいちばんせまいところだけ考えればいいので、最初からウィジェットの高さに応じてテーブルを作っておきます。さらにメモ化して高速化。

何とか木野ちゃん、よろこんでくれました。自分にはこれ以上考えつかないですね。詳しい結果はこちら。なお、メモ化する前の結果はこちら。いちおうすべて通っていますが、木野ちゃんのよろこび方がちがいますね。

コード。

height, width = gets.split.map(&:to_i)
wnd = []
height.times {wnd << (~ gets.chomp.to_i(2) & ("1" * width).to_i(2))}
num = gets.to_i
wgt_size = []
num.times {wgt_size << gets.split.map(&:to_i)}

table = []
height.times do |i|
  ar = []
  (height - i).times {|j| ar << wnd[j, i + 1].inject(&:&)}
  ar.delete(0)
  table << ar
end

num.times do
  memo = {}
  count = 0
  h, w = wgt_size.shift
  if h <= height and w <= width
    wid = widget = ("1" * w).to_i(2)
    table[h - 1].each do |wd|
      if memo[wd]
        co = memo[wd]
      else
        co = 0
        (width - w + 1).times do
          co += 1 if wd & widget == widget
          widget = widget << 1    #ビットシフト
        end
        memo[wd] = co
        widget = wid
      end
      count += co
    end
  end
  puts count
end

これもループは 3重ですが、要素の数が一気に減っています。さらにメモ化でメモされた場合はループは 2重です。
(追記:少し冗長なところを直したらさらに高速化しました。結果。)
 

解説ページのアルゴリズム

ここにアルゴリズムの公式解説があります。その「O(H^2 W^2 ) の解法」という解説をそのまま Ruby に落としてみたのがこれです。しかしこれ、満点がでないのですけれど(結果はこんな具合です)。ダメじゃないですか。


 

絵文字フィボナッチ

どんな言語かわからないですよね。

🐱 = []
🍇, 🍉 = 0, 1
while 🍉 < 1000
  🍇, 🍉 = 🍉, 🍇 + 🍉
  🐱 << 🍇
end
p 🐱
#=>[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]

Ruby です😄 おわかりでしょうがフィボナッチ😆