Haskell
ImageMagick

Haskell でサムネイル #2

More than 1 year has passed since last update.

この記事は Haskell でサムネイルの続きです。まだの方はそちらを先にどうぞ。

imagemagick

サムネイル生成といえば ImageMagick が思い当たります。ImageMagick は Ruby、Python、PHP、Perl、Java、Go、その他いろいろな言語のバインディングがあり、我らが Haskell にも imagemagick というパッケージが存在しました。

存在はしましたが、画像の向きをどうにかする機能は Haskell のバインディングでは提供されていませんでした(0.0.4.1 ではコメントアウトされています)。

ImageMagick

ImageMagick は画像に関する様々な処理がコマンドラインで行えます。例えば、画像ファイルの情報を取得するには以下のように行います。

$ identify original.jpg
original.jpg JPEG 3264x2448 3264x2448+0+0 8-bit sRGB 3.288MB 0.000u 0:00.000

画像の幅と高さだけが欲しい場合、以下のように行います。

$ identify -format "%w %h" original.jpg 
3264 2448

画像をリサイズする場合は以下のように行います。

$ convert -resize 200x150 original.jpg thumb.jpg

今回行いたい処理は、縦横のどちらかが 200px 以内になるようにアスペクト比を維持したまま、Exif の Orientation が設定されていればその向きに補正しつつ、不要な Exif はすべて除去しつつサムネイルを生成する、というものです。

$ convert -auto-orient -strip -resize 200x200 original.jpg thumb.jpg

-resize 200x200 というのが空気を読んでくれる素晴らしいオプションでした。元画像のサイズから計算したり Exif Orientation を取得して回転計算を行ったりする必要はありません。

Haskell から実行する

Sample.hs
import System.Process

main :: IO ()
main = do
  system "convert -auto-orient -strip -resize 200x200 original.jpg thumb.jpg"
  return ()

身も蓋もないんですが、システムコマンドを実行してしまいます。

thumbnail の mkThumbnail' なら、サムネイルの画像サイズが以下のように取得できますが:

  thumb <- mkThumbnail' ((50, 50), (200, 200)) contents
  case thumb of
    (Left _) -> return Nothing
    (Right t) -> return $ Just (fst $ sz t, snd $ sz t)

convert コマンドでサムネイルの画像サイズを取得する場合、例えば以下のようにできます:

  system $ "convert -auto-orient -strip -resize 200x200 " ++ org ++ " " ++ thumb
  wh <- readProcess "identify" ["-format", "%w %h", thumb] []
  let a = " " `splitOn` wh
  return $ Just (read $ a !! 0, read $ a !! 1)

まとめ

Haskell でサムネイルで使用したライブラリは、どちらも Exif Orientation を考慮したサムネイルを生成してくれませんでした。

今回の記事では、オリジナルの JPEG から Exif Orientation を読み取り、回転しているようであれば生成したサムネイルを回転させるか Exif Orientation を付与するか、という方法を検討していました。Haskell で Exif を読み取れる hsexif というパッケージがあります。これは以下のように使用します。

Sample.hs
import Data.Either (either)
import Data.Map.Lazy (fromList)
import Graphics.HsExif (getOrientation, parseFileExif)

file :: FilePath
file = "tools/4f517b31-caea-4ade-9cc5-069d6c9970dd.jpeg"

main :: IO ()
main = do
  eexif <- parseFileExif file
  let exif = either (\a -> fromList []) id eexif
      mio = getOrientation exif
  print mio

getOrientation という便利な関数が提供されており、例えば左に 90 度回転した画像だと、このサンプルは Just (Rotation MinusNinety) を出力します。

ただし、hsexif は Exif の読み取りのみをサポートし、Exif の書き込みはサポートしていないこと、ExifTool を使用すれば Exif の書き込みが可能ではあるが、システムコマンドを実行するのであれば ImageMagick でコマンド一発でサムネイルを生成する方が簡単であることから、今回のような結果となりました。

OpenCV の Haskell バインディングである CV ならできるかも、と思って API を探してみましたが、OpenCV の Resize 相当が見当たらなかったため、実装されていないのだろうという結論です。