とにかく書く

日々の雑感や知り得たことを、とにかく書いています

おっさんの挑戦(画像ビューア作成) 003

おっさんになると、体が劣化してくる。

白髪や皮膚のたるみ、シミなど、外見上のものは仕方がないと諦めるしか無い。
基本的に外見は生きる上で実害はあんまりない。(この考え方が既におっさんか…)
ロマンスグレーで素敵とか、プラスにも働く。いや、素敵ではないな。単なるお世辞か。

デスクワーカーの恐怖の問題は「老眼」。
なんだか字がよく見えなくなる。以前は平気だった 8pt のフォントが妙に小さい。
そんなとき、ふと職場の年かさの人が、暗い色使いでコーディングをしていたことを思い出した。
鴨の羽色の背景色(#008080 : つまり Windows 95 のデスクトップの色)にグレーの文字。見辛い。

ところが最近、背景が白で黒い小さな文字だと明るすぎて見えなくなってくることに気づいた。
なんだかすぐ目が疲れる。
そこで、emacs の color-theme を clolor-theme-dark-blue2 にした。
濃紺の背景に、グレーの文字で読みやすい。SFCのファイナル・ファンタジーの色っぽくて郷愁にかられる。

せっかくだからテストで使う画像も比較的暗い色を選ぼう。
とはいえ、あんまり暗いと繊細なおっさんの心に影を落とすから、やっぱり明るいのを選ぼう。
先日行った愛宕山の写真*1にしてみた。

だけど画像を表示させようとすると、やっぱりコンパイルエラーが出る。
というわけで iced の仕組みや動きをちゃんと調べてみた。

Application トレイト, Sandbox トレイト

ドキュメント によると Application トレイトは、メソッドが Command を返すことで、Command が非同期に(アプリケーションの描画とは別に)実行される。
非同期に実行される必要がなければ、Command の実行がない Sandbox トレイトを使う方がシンプルで良い。

画面ステータスの構造体にこのトレイトを実装することで run メソッドも追加され、GUI が表示される。

Application::view メソッド

画面の表示が定義されたウィジェットを返す。
返り値の型である Element は汎用的なウィジェットの型。
Column と Row は、widget モジュールで定義された型。インスタンスに対してウィジェットを push して並べる。

Column.new().push(ウィジェット).push(ウィジェット)

widget

ウィジェット(ボタン、スライダー、画像、キャンバスなど)は widget モジュール で定義されている。

Image ウィジェット

Image ウィジェットは、Cargo.toml に iced に対して features=["image"] を指示する必要がある。

iced = { version = "0.1.1", features=["image"] }

そうしないと iced_wgpu の Image が参照されてしまい、型違いで Column 型のインスタンスに push できない(コンパイルエラー)。

width や height メソッドで、縦横の比率を保ったまま縮小してくれる。
ただし、これらメソッドで指定できるのは元の画像のサイズまで。
それ以上大きい値を指定しても拡大はしてくれない。

理由
ソースコード をから推察してみる。
Image 型に対する Widget トレイトの実装で、layout メソッドが定義されている。
この layout メソッドの最初で renderer.dimensions(&self.handle) の返り値にて、元画像の幅と高さを width と height にセットしている。
このあと、size の定義で、layout::Limit 型の resolve メソッドで大きさを丸めてしまっているように思える。

で、ようやっと動くようになった。前述の通り、画像のサイズはオリジナル以上には拡大できないみたい。
Canvas ウィジェットで RGB を表示するようにして、拡大を自分で実装してみようかと考えている。

use iced::{
    button, executor, Application, Button, Column, Command, Element, Image, Length, Settings, Text,
};

fn main() {
    Viewer::run(Settings {
        ..Settings::default()
    })
}

// State - the state of my application
struct Viewer {
    width: u16,
    button: button::State,
}

// Message - event that we care about
#[derive(Debug, Clone, Copy)] // derive Debug, Clone, Copy trait
pub enum Message {
    ExpandPressed,
}

impl Application for Viewer {
    // Use default
    type Executor = executor::Default;
    type Message = Message;
    type Flags = ();

    fn new(_flags: ()) -> (Self, Command<Message>) {
        (
            Viewer {
                width: 100,
                button: button::State::new(),
            },
            Command::none(),
        )
    }

    fn title(&self) -> String {
        "atagosan".to_string()
    }

    // Update logic: connect events to functions
    fn update(&mut self, message: Message) -> Command<Message> {
        match message {
            Message::ExpandPressed => {
                self.width += 10;
            }
        }

        Command::none()
    }

    // View Logic
    fn view(&mut self) -> Element<Message> {
        // Use a column: a simple vertical layout
        Column::new()
            .push(
                Button::new(&mut self.button, Text::new("expand the image"))
                    .on_press(Message::ExpandPressed),
            )
            .push(
                Image::new("/home/chrono/rust/kilo/atagosan.jpg").width(Length::Units(self.width)),
            )
            .push(
                // The message
                Text::new(format!("愛宕山({})", self.width)),
            )
            .into()
    }
}

*1:atagosan.jpg