仕事で、URLから画像を取得していい感じにリサイズする要件があったため調べました。
備忘のためメモを書いておきます。
URLから画像の取得
今回はURLから画像を取得して編集することが要件だったので、まずはURLから画像を取得する処理です。
後々の取り回しのためにByteArrayで取得しておきます。
import java.net.HttpURLConnection import java.net.URL fun getImageBytes(url: String): ByteArray { lateinit var conn: HttpURLConnection return try { conn = URL(url).openConnection() as HttpURLConnection conn.requestMethod = "GET" conn.inputStream.readBytes() } finally { conn.disconnect() } }
画像の加工
BufferedImageへ変換
取得した画像を編集するために、BufferedImageに変換します。
// 画像読み込み
javax.imageio.ImageIO.read(imageBytes.inputStream())
画像のリサイズ
縦横の幅を指定して新たなBufferedImageとして描画し直すことでリサイズできます。
import java.awt.image.BufferedImage fun resize(image: BufferedImage, width: Int, height: Int): BufferedImage = BufferedImage(width, height, BufferedImage.TYPE_INT_RGB).also { val g = it.createGraphics() g.drawImage(image, 0, 0, width, height, null) g.dispose() }
画像の切り抜き
BufferdImage.getSubimage()
を使うと、画像の一部を切り出すことができます。
x, yで切り抜きの始点を指定し、そこからwidthとheightで指定した分の画像を切り抜いた新しいBufferdImageを生成します。
このとき、指定した座標などが画像の範囲外になるとエラーになるので注意してください。
image.getSubimage(x, y, width, height)
画像の保存
BufferdImageはOutputStreamやFileのようなオブジェクトに書き込むことで保存できます。
// ByteArrayOutputStreamに書き込んでByteArrayに変換する場合 ByteArrayOutputStream().let { ImageIO.write(croppedImage, "PNG", it) it.toByteArray() } // Fileオブジェクトに直接書き込む場合 ImageIO.write(croppedImage, "PNG", File("ファイル名.png")
(おまけ)画像をいい感じにリサイズして切り抜く
今回求められていた要件は、与えられた画像を指定のサイズに余白なく収まるようにリサイズした上で、中心を基準に画像を切り抜くというものでした。
文字だけだと伝わりづらいと思うので例を示すとこんな感じです。
この処理を実装するためには、
- 元画像と描画先の比率からリサイズの倍率を求めてリサイズする
- 描画領域に応じて切る方向を決める
- 中心から適切に画像を切り抜く
という手順が必要でした。
欲しいサイズに合わせてリサイズ
縦、横それぞれの元画像、編集後の比率を求めて、差の大きい方を基準の倍率としてリサイズします。
import java.awt.image.BufferedImage import kotlin.math.ceil fun resize(image: BufferedImage, width: Int, height: Int): BufferedImage { // 横幅・縦幅の比率を計算して、大きい方を基準にリサイズする val widthScale = width.toDouble() / image.width.toDouble() val heightScale = height.toDouble() / image.height.toDouble() val scale = if (widthScale > heightScale) widthScale else heightScale val resizeWidth = ceil(image.width * scale).toInt() val resizeHeight = ceil(image.height * scale).toInt() return BufferedImage(resizeWidth, resizeHeight, BufferedImage.TYPE_INT_RGB).also { val g = it.createGraphics() g.drawImage(image, 0, 0, resizeWidth, resizeHeight, null) g.dispose() } }
中心から画像を切り抜く
元画像と出力結果の向きから画像の切り取り方向を決定し、切り抜きます。
切り抜く座標の原点は画像の中心から出力結果の半分を引くことで求めることができます。
import java.awt.image.BufferedImage fun crop(image: BufferedImage, width: Int, height: Int): BufferedImage? { // 元画像が横長か縦長かを判定 val isHorizontalImage = image.width > image.height // 出力結果が縦長か横長かを判定 val isHorizontalView = width > height return when { // 横長の画像を縦長のViewに表示する場合 isHorizontalImage && !isHorizontalView -> { val x = (image.width.toDouble() / 2 - width.toDouble() / 2).toInt() image.getSubimage(x, 0, width, height) } // 縦長の画像を縦長のViewに表示する場合 else -> { val y = (image.height.toDouble() / 2 - height.toDouble() / 2).toInt() image.getSubimage(0, y, width, height) } } }
サンプル実装
おまけで記載している処理については下記のファイルに一通りが記載されています。
手元で実行すれば、冒頭であげた画像のような出力結果が得られます。
sandbox/20210320.kt at 686f532c6dbddda43262df1673e015870a5bf5ad · beatdjam/sandbox · GitHub
おわりに
Java標準のライブラリを使って、思いの外自由に画像の編集が行えることがわかりました。
ググって出てくるのはほとんどJavaの実装だったので、Kotlinで同じようなことをしてみたい方の役に立てばいいなと思っています。
おまけの部分の実装は、思いついた実装でどうにかしたという感じなので、より良い方法をご存知の方はコメントやTwitterなどで教えて下さい。