Rustのpixelsで円とか点とか描く(Rust製ラスタライザ)
pixelsを使う場合、別途ウィンドウ管理システムを用いる必要がある 記述時:2024-08-13
もしかしてアップデートがあった?
ウィンドウ作成できるんだったらwinitとかだけでいいんじゃないの?
Winit is designed to be a low-level brick in a hierarchy of libraries. Consequently, in order to show something on the window you need to use the platform-specific getters provided by winit, or another library.
とのことなので、ウィンドウを生成するwinit単体だけでは絵は描けない
(描けてもプラットフォーム特有のコードをうにゃうにゃする必要がある)
Before you can create a Window, you first need to build an EventLoop. This is done with the EventLoop::new() function.
code:rs
use winit::event_loop::EventLoop;
let event_loop = EventLoop::new().unwrap();
Then you create a Window with create_window.
なるほど
??? このコードでcreate_window作ろうとしたらDeprecatedだと言われた...??
EventLoop内にあるやつじゃなくてActiveEventLoopのものを使えと言われた
APIがunstableなのか...?
どうやらAppという構造体を作ってメインの処理を記述すればActiveEventLoopが得られるっぽい?
複雑だなあ
すぐウィンドウをセットアップできる
APIがStableであることが期待できる
目標:ここのコードを参考に、半径15pxの円を描くプログラムを作る というわけでこうなった。
code:rs
env_logger = "0.11.5"
log = "0.4.22"
pixels = "0.13.0"
AC.icon fltkをセットアップしてウィンドウを表示する できた(下を参考に)
code:rs
use fltk::app;
use fltk::prelude::{GroupExt, WidgetExt};
use fltk::window::Window;
const WIDTH:u32 = 640;
const HEIGHT:u32 = 480;
fn main() {
let app = app::App::default();
let mut win = Window::default()
.with_size(WIDTH as i32, HEIGHT as i32)
.with_label("Hello Pixels");
win.make_resizable(true);
win.end();
win.show();
app.run().unwrap();
}
https://scrapbox.io/files/66b9d523c77812001c4e9a7c.png
AC.icon 円を書く
ピクセル値の書き換えができることを確認してみる
サンプルコードの流れに沿って書くことを考えると、円を書く方が手早く済むと考えた
まずは点一個を描く関数よりも円を描く機能を作ってしまう
app.run().unwrap();を次のように置き換えて、pixelsをセットアップしapp.wait, flush, awakeを使って書き直す
code:rs
...
fn main() -> Result<(), Error> {
...
win.show();
// pixelsをセットアップ
let mut pixels = {
let pixel_width = win.pixel_w() as u32;
let pixel_height = win.pixel_h() as u32;
let surface_texture = SurfaceTexture::new(pixel_width, pixel_height, &win);
Pixels::new(WIDTH, HEIGHT, surface_texture)?
};
// メインループ
while app.wait() {
if let Err(err) = pixels.render() {
panic!("{}", err);
}
app::flush();
app::awake();
}
Ok(())
}
さて、フレームバッファに書き込んでいく
pixels.frame_mut().chunks_exact_mut(4).enumerate()によって、ピクセル番号とピクセル配列を一つ一つ得る。
まず、ピクセルの番号iからピクセル位置x, yを求める
そして、[0xff, 0xff, 0xff, 0xff]などの色を一つ一つのピクセルに割り当てていく。
code:rs
fn sq(x:i32) -> i32 { x * x }
code:rs
let radius = (WIDTH / 5) as i32;
let (cx, cy) = ((WIDTH / 2) as i32, (HEIGHT / 2) as i32);
while app.wait() {
if let Err(err) = pixels.render() {
panic!("{}", err);
}
for (i, pixel) in pixels.frame_mut().chunks_exact_mut(4).enumerate() {
let x = (i % WIDTH as usize) as i32;
let y = (i / WIDTH as usize) as i32;
let rgba = if sq(x - cx) + sq(y - cy) < sq(radius) {
} else {
};
pixel.copy_from_slice(&rgba);
}
app::flush();
app::awake();
}
https://scrapbox.io/files/66b9f828e26093001c3ac98c.png
AC.icon resizeに対応できるようにする
surface_resize.replaceメソッドをresize変更時に実行するようにして
コールバック関数から特定のリソースを触る必要が出てくるため、RcおよびRefCellが必要となる。 code:rs
let surface_size = Rc::new(RefCell::new(None));
let surface_resize = surface_size.clone();
win.resize_callback(move |win, _x, _y, width, height| {
let scale_factor = win.pixels_per_unit();
let width = (width as f32 * scale_factor) as u32;
let height = (height as f32 * scale_factor) as u32;
surface_resize.borrow_mut().replace((width, height));
});
その後、こんな感じでウィンドウサイズ変更をpixels側に伝達することでウィンドウ変更を実現する 変更に失敗したらquit()する。
code:rs
if let Some((width, height)) = surface_size.borrow_mut().take() {
if let Err(err) = pixels.resize_surface(width, height) {
log_error("pixels.resize_surface", err);
app.quit();
}
}
AC.icon ログを出力する環境を整える
こう言う関数を事前に定義する
code:rs
fn log_error<E: std::error::Error + 'static>(method_name: &str, err: E) {
error!("{method_name} failed: {err}");
for source in err.sources().skip(1) {
error!("\tCaused by: {source}");
}
}
AC.icon 点を描く関数を用意する。
pixels.frame_mut()をどうにか使えないですかね?appbird.icon
pixel.frame_mut()[start..end].copy_from_slice(&[0x00, 0x00, 0x00, 0x00])
という処理をラップすれば作ることができる
ただし、start: usize, end:usize
とここまで振り返ってみると、必要以上に複雑になってるなと思った
ここまで色々機能を詰め込む必要はないので、話をシンプルにするために一旦minifbに戻ることにした。