0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Ruby] MiniMagick と二分探索を使って、画像を任意のサイズ以下に圧縮する

Last updated at Posted at 2025-06-17

やりたいこと

きのこ.heic という 3.3 MB の画像ファイルがあります。

  • この画像を 1.0 MB 以下の JPEG 形式に圧縮したい
  • 画質を最大限保つため、ファイルサイズはなるべく大きくしたい

きのこ.jpg

方法

MiniMagick という Gem を使って、画像を JEPG に変換し、なおかつ MiniMagick::Image#quality で画質 (1〜100) を指定します。

画質を 100, 99, 98 と小さくしていき、ファイルサイズが 1.0 MB 以下になる最大の画質を探すのはどうでしょう?しかし、これでは目的の画質が小さい場合に処理の回数が増えてしまいます。

そこで、目的の画質を検索するために二分探索のメソッドである Array#bsearch あるいは Range#bsearch を使い、効率化を図ります。

$ ruby -v
ruby 3.4.3 (2025-04-14 revision d0b7e5b6a0) +PRISM [arm64-darwin24]
$ gem install mini_magick activesupport
require 'mini_magick'
# ファイルサイズの計算や表示に ActiveSupport コア拡張が便利なので使用する。
require 'active_support'
require 'active_support/core_ext'

include(ActiveSupport::NumberHelper)

def compress_until_less_than_or_equal_to(filepath:, limit:, debug: false)
  compressed_image = nil

  # bsearch では条件を満たす最小の値を見つけるので昇順に並べておく。
  (1..100).bsearch do |n|
    quality = 100 - n

    tmp_image = MiniMagick::Image.open(filepath)
    tmp_image.format('jpeg')
    tmp_image.quality(quality)

    puts("画質 #{quality}: #{number_to_human_size(tmp_image.size)}") if debug

    less_than_or_equal_to = tmp_image.size <= limit

    # limit 以下の画像の中で一番サイズが大きいものを保持しておく。
    if less_than_or_equal_to
      compressed_image ||= tmp_image
      compressed_image = [tmp_image, compressed_image].max_by(&:size) 
    end

    less_than_or_equal_to
  end

  compressed_image
end

# ~/Downloads/きのこ.heic を読み込む。
filepath = Pathname(Dir.home).join('Downloads/きのこ.heic')

# 目的の画質を検索する過程を出力しながら画像を圧縮する。
compressed_image =
  compress_until_less_than_or_equal_to(
    filepath: filepath,
    limit: 1.megabyte,
    debug: true
  )
# 画質 50: 1.8 MB
# 画質 25: 1.19 MB
# 画質 12: 767 KB
# 画質 19: 1020 KB
# 画質 22: 1.09 MB
# 画質 21: 1.06 MB
# 画質 20: 1.03 MB

# 1.0 MB (1,024 KB) 以下である。
number_to_human_size(compressed_image.size)
#=> "1020 KB"

# ~/Downloads/きのこ.jpg に書き込む。
compressed_image.write(Pathname(Dir.home).join('Downloads/きのこ.jpg'))

compress_until_less_than_or_equal_to メソッド実行時の標準出力を見ると、以下のように探索しているのがわかります。

バイナリサーチでファイルを圧縮.jpg

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?