RのnativeRasterを駆使して涅マユリを輝かせる

この記事でやること

この記事では、R言語を使って、図表(画像)を輝かせる方法を紹介します。

輝かせる画像として、ここでは、BLEACH 590話(コミックス65巻)から涅マユリが「偉大な相手というのは 輝いて見えるものだヨ」と言っているコマを使います。画像をnativeRasterとして取り込み、エフェクトを加えることで、マユリ様を輝かせます。扱う画像はネタに振り切っていますが、やることとしては、Rでやるにはけっこうガチめの画像処理です。

ここで、画像はpublic/mayuri.jpgとして用意しています。これはインターネットで適当に検索して拾ってきた、715x1050の縦長の画像です。だいたい次のような見た目をしています。

BLEACH 590話のコマ

この画像でなければいけない必要性がとくにないので、申し訳程度にBLEACHのことにも触れておきますが、BLEACHの漫画は、このような表情がよくわかる顔のアップで発話しているコマへの持っていき方が上手いですよね。インターネットでネタにされがちな「いつから~錯覚していた?」や「何…だと…」も、そういう類のコマです。こうしたコマを切り抜いてきたときの「絵になる感じ」もすごいのですが、読んだときにこういうオシャレな会話が自然なテンポで入り込んでくる感じが、BLEACHの魅力の一つだと思います。

ぜんぶnativeRasterなんだヨ

さて、この画像は、漫画のコマとしてはこの次のコマを含む2コマからなっているので、クロップして上側のコマだけを取り出しつつ、720x576に強制的に(アスペクト比を維持せずに)リサイズしたうえで、PNG画像を経由してnativeRasterとして読み込んでおきます。

path <- here::here("public/mayuri.jpg")
rast <-
  magick::image_read(path) |>
  magick::image_crop(
    geometry = magick::geometry_area(710, 548, 0, 0)
  ) |>
  magick::image_resize("720x576!") |>
  magick::image_convert("png") |>
  magick::image_write(path = NULL) |>
  fastpng::read_png(type = "nativeraster")

ここで、nativeRasterについて説明しておきましょう。nativeRasterというのは、R言語における型(S3クラス)の一つで、画像を表現するのに用いられる特殊な配列です。

「ネイティブ・ラスター」とはいうものの、これは、たとえば地理空間データを扱うときに出てくる「ラスタ画像」のようなものとは異なるオブジェクトで、一般のRユーザーがこのnativeRasterを直接触る機会はたぶんほとんどないだろうと思われます。そもそもの話、こんな型がR言語にあることを知らなかったという人も少なくないかもしれません。

これはどういうオブジェクトかというと、たとえば上の720x576の画像を表現するnativeRasterは、次のような構造をしています。

dim(rast)
#> [1] 576 720
class(rast)
#> [1] "nativeRaster"
typeof(rast)
#> [1] "integer"

str(rast)
#>  'nativeRaster' int [1:576, 1:720] -1 -1 -1 -1 -1 -1 -1 -1 -131587 -65794 ...
#>  - attr(*, "channels")= int 4

見た目こそ、見ての通り、nativeRasterというクラスが付いただけの整数ベクトルです。この整数ベクトルには、画像の高さ・幅に対応する次元があります。しかし、見た目こそinteger matrixに見えるものの、通常の行列のような列指向(column-major)ではなく、行指向(row-major)でデータが格納されているため、内部的には添え字の順序が逆転しています。

nativeRasterは、このようなかたちで、各ピクセルの色を32ビット符号なし整数によって表現している値を詰め込んだ配列です。内部的には32ビット符号なし整数なのですが、R言語には符号なし整数に相当する型が存在しないため、Rコンソール上では通常の符号あり整数として表示されています。

たとえば、-131587Lというのは、このピクセルに置かれるべきRGBA値をそれぞれ8ビットずつ使って表現しているデータが符号あり整数として見えているもので、カラーコードとしては#FDFDFDFFに相当します。

colorfast::int_to_col(c(-131587L, -1L, 0L)) # colorfastやfarverパッケージで変換できる
#> [1] "#FDFDFDFF" "#FFFFFFFF" "#00000000"
colorfast::col_to_int(c("magenta", "gray90", "#c0ffeedd")) # 色名・カラーコードからの変換
#> [1]     -65281   -1710619 -571539520

このかたちの整数は、Rのグラフィックデバイスにおいてピクセルの色を表現するのに用いられているデータ形式そのままであるため、グラフィックデバイスで描画したい画像をnativeRasterとして渡すと、他のデータ形式を使うのと比較して非常に高速に描画することができます。

grid::grid.newpage()
grid::grid.raster(rast, interpolate = FALSE)
クロップした画像

一般的なユースケースとしては、このうえでやっていたように、fastpngやpng, jpegといった画像ファイルを読み込むRパッケージの一部で、この形式で画像を読み込むことができます。そうして読み込んだnativeRasterは、grid::grid.raster()に渡すことで直接描画できるほか、ggplot2::annotation_raster()を使うことでggplot2のグラフのなかに埋め込んで表示したりできます。

当初の目的である「図表(画像)を輝かせる」を実現したい場合、ggfx::with_bloom()を使えばggplot2の任意のレイヤーにbloom風のエフェクトを加えることができるため、たとえば次のようにすると、マユリ様を輝かせることができます。

library(ggplot2)
library(ggfx)

gp <-
  ggplot() +
  with_bloom(
    annotation_raster(rast, -Inf, Inf, -Inf, Inf),
    strength = 0.7
  ) +
  labs(title = "元絵:BLEACH 590話(コミックス65巻)より") +
  theme_minimal()

print(gp)
ggplot2で表示した画像

nativeRasterを操作する

これでたしかに光ってはいるはずなのですが、マユリ様の威光を表現するうえでは、ここはやはりもっと派手に輝かせたいですよね?

ggfxにはggfx::with_custom()という関数があり、この関数のfilter引数から独自関数を指定すると、ある程度自由にエフェクトを加えることができます。ここに与える独自関数はnativeRasterを第一引数として受け取り、同じサイズのnativeRasterを返すようなものである必要があります。

ただ、すでに説明したように、nativeRasterはかなり特殊な形式のオブジェクトであるため、Rで操作するのには基本的に向いていません。naraというRパッケージを使うことでRからでもある程度は編集できますが、今回のようにイメージフィルター(エフェクト)を書きたいという場合には、自分でC/C++などのコードを書いて、Rパッケージとして整備するのがかえって簡単だろうと思います。

たとえば、私が最近つくっているaznyanというRパッケージを使うと、次のようにしてエフェクトをかけることができます。実際にどういう処理をしているかは、興味があればコードを読んでみてください。

ggplot() +
  with_custom(
    annotation_raster(rast, -Inf, Inf, -Inf, Inf),
    filter = \(x, ...) {
      aznyan::preserve_edge(x) |>
        aznyan::swap_channels(to = c(2, 1, 1, 3)) |>
        aznyan::diffusion_filter(...)
    },
    factor = 5,
    offset = .7
  ) +
  labs(title = "元絵:BLEACH 590話(コミックス65巻)より") +
  theme_minimal()
ggfxで光らせた画像

nativeRasterをつくる

自前でエフェクトを用意するのはハードルが高いかもしれませんが、nativeRasterを用意するだけだったら、Rだけでもわりと柔軟に実現できます。ggplot2やpatchworkパッケージを使いつつ、ragg::agg_capture()を使ってグラフィックデバイスの状態をキャプチャすれば、かなりいろいろなことができるはずです。

patchworkパッケージのpatchwork::wrap_elements()などの関数を使うと、nativeRasterをggplot2オブジェクトとほとんど同じような感覚でレイアウトすることができます。また、ragg::agg_capture()は、その時点におけるグラフィックデバイスの状態をキャプチャして返すような関数をつくるfactoryで、次のような使い方をすると、ようするにRのグラフィックデバイスに描けるものなら何であれ、nativeRasterとしてキャプチャすることができます。

library(patchwork)

rast2 <- aznyan::preserve_edge(rast)

green <- rast2 |>
  aznyan::swap_channels(to = c(2, 1, 1, 3)) |>
  aznyan::diffusion_filter(factor = 5, offset = .7) |>
  wrap_elements(full = rast, panel = _)
cyan <- rast2 |>
  aznyan::swap_channels(to = c(2, 1, 0, 3)) |>
  aznyan::diffusion_filter(factor = 5, offset = .7) |>
  wrap_elements(full = rast, panel = _)
red <- rast2 |>
  aznyan::swap_channels(to = c(1, 2, 2, 3)) |>
  aznyan::diffusion_filter(factor = 5, offset = .7) |>
  wrap_elements(full = rast, panel = _)
magenta <- rast2 |>
  aznyan::swap_channels(to = c(1, 0, 2, 3)) |>
  aznyan::diffusion_filter(factor = 5, offset = .7) |>
  wrap_elements(full = rast, panel = _)

cap <- ragg::agg_capture(width = 720, height = 576)

pw <-
  ((green + cyan) / (red + magenta)) +
  plot_annotation(
    title = "まぶしい理由の方は訊いてないんですけど?",
    caption = "元絵:BLEACH 590話(コミックス65巻)より",
    theme = theme(
      plot.title = element_text(size = 32, colour = "red"),
      plot.caption = element_text(size = 20, colour = "blue")
    )
  )

print(pw)

pict <- cap(native = TRUE)
dev.off()
#> agg_png 
#>       2

grid::grid.newpage()
grid::grid.raster(pict, interpolate = TRUE)
patchworkでレイアウトした画像

このragg::agg_capture()はめちゃくちゃ強力で、ggfxも実のところ、ggplot2のレイヤーをラスタライズするのにこれを使っているっぽいです。この関数は、たとえばVSCode + httpgdを使っていて、SVGで描画するには重いグラフをプレビューするのにグラフィックデバイス全体をラスタライズしたいときとかに使ったりすることもできます。覚えておくと何かと便利です。

むすび

この記事では、Rで図表(画像)を輝かせることをゴールとして、ここまでにnativeRasterを扱うさまざまな方法を見てきました。これも私が最近つくったmixboxrというパッケージを使うと、2枚のnativeRasterを混色することができるので、ここまでに用意した画像を次のように混ぜることで、無事にマユリ様を派手に輝かせることができます。

grid::grid.newpage()
mixboxr::lerp(pict, rast, 0.4) |>
  grid::grid.raster(interpolate = TRUE)
Mixboxで混色した画像

実際にnativeRasterをバリバリに触る機会は、たぶんふつうにRを使っているかぎり訪れないだろうと思いますが、nativeRasterに関するまとまった情報は実は英語でもほぼ皆無であり、現状、検索してもほとんど何もヒットしません。なんとなくであっても、これはこういうもので、こういうふうに使うことができるんだと覚えておくと、必要に迫られたときに便利かもしれません。記憶の片隅にでも、ぜひ覚えておいてください。

This article was updated on 10月 29, 2025