Rustで使いまわしの良いactix-web製HTTPサーバモジュールの基礎を作る

目的

必要にかられてRustでHTTPサーバを実装することになりました。

HTTPサーバは何かと使い勝手が良く、今後も気軽に立てたくなることがあるはずなので、ここで一つすごくシンプルなHTTPサーバのモジュールを書いてみました。

せっかくなのでRustが誇る高速なHTTPサーバであるactix-webを使い、startとstopのインターフェイスを持たせました。

正直言うと、actix-webのexamplesのレポジトリにはとても参考になるソースコードがたくさんあるので、私もそれを使って実装しています。皆さんもactix-webを使うときには、このレポジトリを参考にしたら良いと思います。

ただ、GoogleでHTTPサーバを調べたときに、もっとお手軽なソースコードがヒットしてもよかろう、という気持ちがあり、この記事を書くことにしました。

使用したRustのバージョンは、1.40。

ソースコード

Cargo.toml

dependenciesのところだけ抜粋しています。 actix-webと、そのランタイムを扱うactix-rtを使いました。

[dependencies]
actix-web = "2.0.0"
actix-rt = "1.0.0"

http_server.rs

使っている構造体などがどのcrate由来のものなのかわかるように、あえてuseを使わずに書いています。

エラーハンドリングはほとんどunwrap()しています。

extern crate actix_rt;
extern crate actix_web;

pub struct HTTPServer {
    server: Option<actix_web::dev::Server>,
}

impl HTTPServer {
    pub fn new() -> HTTPServer {
        HTTPServer { server: None }
    }

    pub fn start(&mut self) {
        self.stop();

        let (tx, rx) = std::sync::mpsc::channel();

        std::thread::spawn(move || {
            let mut system = actix_rt::System::new("start_server");
            let server = actix_web::HttpServer::new(|| {
                actix_web::App::new()
                    .service(
                        actix_web::web::resource("/ok").to(|req: actix_web::HttpRequest| {
                            println!("{:?}", req);
                            actix_web::HttpResponse::Ok().body("ok")
                        }),
                    )
                    .service(
                        actix_web::web::resource("/ng").to(|req: actix_web::HttpRequest| {
                            println!("{:?}", req);
                            actix_web::HttpResponse::Ok().body("ng")
                        }),
                    )
            })
            .bind("0.0.0.0:8888")
            .unwrap()
            .run();
            tx.send(server.clone()).unwrap();
            system.block_on(server).unwrap();
        });

        self.server = Some(rx.recv().unwrap());
    }

    pub fn stop(&mut self) {
        if let Some(server) = &self.server {
            let mut system = actix_rt::System::new("stop_server");
            system.block_on(server.stop(true));
        }
        self.server = None;
    }
}

main.rs

mod http_server;

fn main() {
    let mut server = http_server::HTTPServer::new();

    server.start();

    std::thread::sleep(std::time::Duration::from_secs(10));

    server.stop();
}

解説

HTTPServerをstart()させるときには、裏で勝手に動き出してほしいので、別スレッドでactix-webのHTTPサーバを起動させています。

actix_web::HttpServer::new()などでインスタンスを作成できますが、サーバを止めたいときには再びこのインスタンスを使う必要があります。

そのため、HTTPServer構造体の中に保存できるようにします。OptionでServerを包み、HTTPServer構造体をnew()したときには中身がNone、start()したらインスタンスを入れられるようにしました。

pub struct HTTPServer {
    server: Option<actix_web::dev::Server>,
}

ここで問題になるのが、スレッド内で作ってしまったインスタンスをどうやってメンバ変数self.serverに入れるのかです。

今回は、std::sync::mpsc::channel()を使って、スレッドの中から外へのチャンネルを作成しています。 この抜け道を辿って、サーバのインスタンスをスレッドの外へ逃し、HTTPServer構造体のメンバ変数として保存しています。 (The Bookのこちらの記事が参考になります。)

また、今回のHTTPServerを実装する上で、呼び出す側が「asyncを付けなきゃいけない」ということを意識させたくない作りにしたかったので、awaitは使わず、system.block_on(server)しています。

main.rsでは10秒間だけ動くHTTPServerを実装しています。

受け付けるHTTPリクエストのパスは「/ok」「/ng」だけ実装しました。 ここは、その時々に応じて修正しましょう。 呼ばれるパスごとに関数を用意したり、テンプレートエンジンを使うのも良いでしょう。

まとめ

これを使いまわせば、スムーズにHTTPサーバが作れます。

もう少し複雑なことでも、actix-webのexamplesのレポジトリにはとても参考になるソースコードがたくさんあるので、このレポジトリを参考にすると良いです。

f:id:toyamaguchi:20191221150338p:plain

Rust GUI crateの調査

私はRustでOpenGLを使った3Dプログラミングの実装を行っています。

それと関連して、RustでGUI系のcrateを利用して、その使い心地を調べるようなことをよくしています。

ここにその調査結果を少しずつ残して、どのcrateが目的に合っているのか記述していきたいと思います。

ちなみに、私の観点で大切にしたいことは、軽快に動く、導入が楽、ウィジェットが充実、クロスプラットフォームだったら申し分ない、といったところです。

(ちょっとずつ書いていくので、お楽しみに!)

f:id:toyamaguchi:20191221150338p:plain

仕組みを学べるシンプルなターミナルエミュレータ「eduterm」

私たちがよく使用するターミナル。

毎日使っているはずなのに、あまりその仕組みを知らない方も多いはず。

先日、私も思い立ってxtermのソースコードを読んでみようしたら、C言語のifdefの嵐に圧倒され、途中で中断してしまいました。

xtermは様々なプラットフォームで動作することを期待されているので、ifdefでたくさんの場合分けをしており、思いのほか読みづらいのです。

もっとシンプルなターミナルはないものかと、インターネットをさまよっていたところ、ちょうどよいターミナルを発見しました。

それが、この「eduterm」です。

edutermのソースコードは、たった1つのソースコードeduterm.cに書かれており、448行しかありません。 しかも、たくさんのコメントを残してくれていて、とても親切なソースコードです。

コンパイルして実行すると、Xウィンドウシステムをサポートしており、ウィンドウが立ち上がります。 (ウィンドウの処理も、ターミナルとしての処理も、そしてコメントもたくさん入っているのに、この程度の行数で済んでいることに驚きです。)

f:id:toyamaguchi:20200131211132p:plain
edutermの起動

ターミナルは内部で擬似端末を用意し、シェルを起動して、入力を待ち受けるようになります。

「ls」とコマンドすれば、その返答が返ってきます。

f:id:toyamaguchi:20200131211154p:plain
lsコマンドの入力

ただし、「vi」とコマンドしようものなら、わけのわからないエスケープシーケンスがたくさん表示されてしまいます。

viはlsと違って、出力に複雑なことをしています。 viは画面上の適切な位置に、文字やカーソルを表示しなければいけないため、「VT100」というプロトコルを使っています。 そして、edutermはVT100のエスケープシーケンスをサポートしていないのです。

f:id:toyamaguchi:20200131211218p:plain
viコマンドの起動

正直、このままでは普段使いのターミナルになることはないでしょう。

しかし、ターミナルの基本的な動きを学習するには、最適なソースコードなのです。

edutermを紹介している作者のブログ記事がこちらにあるので、興味のある方は合わせて読んでおくとよいかもしれません。

「出来の悪い子ほどかわいい」とよく言いますが、このターミナルをベースにして少しずつ鍛え上げていき、VT100の機能を追加し、フォントを変えたり、色を変えたりして、自分たちのターミナルを作ってみるのも楽しいですね!

RustのLLVM IRでプログラム分析ことはじめ

この記事は、Rustその2 Advent Calendar 2019の22日目の記事です。

はじめに

Rustのコンパイラは内部でLLVMを利用しています。

LLVMとはいわゆるコンパイラ基盤というもので、Rustのコンパイル側でLLVMの中間コード(IR)まで生成できたら、あとはLLVM側が最適化や実行ファイルの作成までを行ってくれます。

Rust Blogの記事「Introducing MIR」の中にある画像が大変わかりやすいので、そちらを引用させていただきます。

f:id:toyamaguchi:20191221104040p:plain
ビルドの流れ

このコンパイラ基盤の視点から言うと、Rustコンパイラはフロントエンド、LLVM側はバックエンドという位置づけです。 LLVMが様々な環境で動くプログラムを生成できるからこそ、Rustも多くの環境で動くプログラムを比較的容易に生成できるわけです。 プログラムの最適化技術についても、LLVM側でそのノウハウを洗練させていけば、LLVMを利用しているプログラミング言語すべてで洗練されていきます。

LLVMコンパイルをする過程では、「Pass」という名前のモジュールにIRを何度か通すことになります。 Passは入力としてIRを受け付け、それを最適化して、IRを出力するフィルタのようなものです。 それぞれ異なる最適化手法が実装されたPassをいくつか通し、IRを洗練させていくようです。

これについては、Adrian Sampson氏のブログ記事「LLVM for Grad Students」(日本語記事)が初心者向けで大変詳しくてありがたいです。(むしろ、この記事をRustに適用したのが本記事と言っても過言はないです。) このブログ記事から、LLVMコンパイルの流れの図を引用したいと思います。

f:id:toyamaguchi:20191221110800p:plain
LLVMコンパイルの流れ

また、LLVMはただのコンパイラ基盤として使われるだけでなく、ソフトウェアの分析ツールとして使われることもあります。 ソースコードコンパイルする際に得られるメタデータを使って、ソフトウェアの構造を明らかにしたり、データの危険な使用がないかを調査するのです。 その調査をする際にも、さきほどのPassを使ってメタデータを取得していくようです。

前置きが長くなりましたが、このブログ記事ではRustのソースコードからLLVM IRを生成し、それをPassに読み込ませてプログラム分析をする「一歩目」を踏み出そうと思います。

LLVMのダウンロード

どうもRustはLLVM 9.0を使っているようなので、それをダウンロードします。

私のLinux環境はDebian Linuxなのですが、DebianUbuntuのユーザーにはaptでインストールできるnightly packageというものがあるようなので、今回はそれを使ってみます。

このサイトに従って、次のコマンドを実行しました。

bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)"

2019年12月21日現在では、LLVM 9.0.1がダウンロードできたようです。

Passの作成

LLVM 9.0の仕様に沿ったPassを用意します。

今回はPassの作り方も詳しくわからないため、次のレポジトリを利用します。 これはLLVMのPassのいくつかの実装例を公開してくれているレポジトリです。

このレポジトリの中には、PassのHelloWorldがありますので、それを使っていきます。 CMakeLists.txtとHelloWorld.cppをダウンロードしてましょう。

ただし、どうもこのままできあがったPassを使うと、使用した最後にSegmentation Faultが起こってしまうようです。 調べてみたところ、GitHubのIssueStackoverflowに似た問題があがっていたので、この解決方法を導入します。 Passのコンパイル時に、リンカオプションの-Wl,-znodeleteを追加することで、共有オブジェクトのアンロードを防ぎ、Segmentation Faultを防ぐことができるそうです。

CMakeLists.txtの中に、次に記すような行を追加しておきましょう。

if(NOT LLVM_ENABLE_RTTI)                                
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")   
endif()                                                 
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,-znodelete")    # add this line!

ちなみに、コンパイル方法は次のようになります。(一般的なcmakeの方法です。) 2つのファイルが置いてあるフォルダ内で実行しましょう。

mkdir build
cd build
cmake ..
make

これでlibHelloWorld.soというPassファイルが完成します。

RustのソースコードLLVM IRに変換

今回はRustのソースコードとして、「cargo new」してできるデフォルトのソースコードを利用します。 プロジェクトフォルダを作って、中に入りましょう。

cargo new hello_world
cd hello_world

rustcコマンドでLLVM IRを作成するコマンドは、次のようになります。 このコマンドで、人間が読めるLLVM IRのソースコードである、main.llが生成されます。

rustc src/main.rs --emit=llvm-ir

ただ、実際のところPassが読み込むのはLLVM IRのビットコードです。 rustcコマンドでLLVM IRのビットコード生成しましょう。 main.bcが生成されます。

rustc src/main.rs --emit=llvm-bc

Passによる分析

今回のPassはソースコード上の関数を読み込む際に実行される「FunctionPass」です。 しかも、関数の名前と引数の数を表示するだけの簡単なものなので、実は「分析」というほど難しいものではありません。

さっそくPassを使ってmain.bcを読み込んでみましょう。「opt」コマンドというものを使って実行します。

opt-9 -load pass-f/build/libHelloWorld.so --legacy-hello-world -o output.bc main.bc

これを実行すると、標準出力に次のような内容が出力されました。

Visiting: _ZN3std2rt10lang_start17hf1171c84b02c6532E (takes 3 args)
Visiting: _ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17h7ce99b67d7e681a9E (takes 1 args)
Visiting: _ZN3std3sys4unix7process14process_common8ExitCode6as_i3217h8b78488b8649031eE (takes 1 args)
Visiting: _ZN4core3fmt9Arguments6new_v117hcb57928db4a7c5fcE (takes 5 args)
Visiting: _ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17hfa439257376c02c0E (takes 1 args)
Visiting: _ZN4core3ops8function6FnOnce9call_once17h04664e48e200f143E (takes 1 args)
Visiting: _ZN4core3ptr18real_drop_in_place17h11442e413e5b1531E (takes 1 args)
Visiting: _ZN54_$LT$$LP$$RP$$u20$as$u20$std..process..Termination$GT$6report17hfd48e48bde5a14dcE (takes 0 args)
Visiting: _ZN68_$LT$std..process..ExitCode$u20$as$u20$std..process..Termination$GT$6report17h7035bf1bc5cd901aE (takes 1 args)
Visiting: _ZN4main4main17h9e0ea2b04575dfdbE (takes 0 args)
Visiting: main (takes 2 args)

HelloWorld.cpp内でいうところの次のコードが、関数の情報を表示しているところです。

void visitor(Function &F) {
    errs() << "Visiting: ";
    errs() << F.getName() << " (takes ";
    errs() << F.arg_size() << " args)\n";
}

IRのビットコードには、main関数以外にもプログラムの実行に関わる目に見えていなかった関数があったことがわかりますね。

まとめ

Rustの内部では、コンパイラ基盤のLLVMを使っています。

このLLVMを使うとプログラム分析を行うことができます。

今回はFunctionPassという種類のPassを作成しました。

Passの種類は他にもModulePass、CallGraphSCCPass、BasicBlockPassなど、興味深い名前のものもあります。

いろいろな種類のPassを作ってみて、Rustのソースコードを分析できるようにしたら楽しそうですね。

参考文献

f:id:toyamaguchi:20191221150338p:plain

技術同人誌執筆中のコンテンツの抜け漏れ対策スクリプト

この記事は技術同人誌 Advent Calendar 2019の12月8日分の記事となります。

私は、今年の9月に技術書典7に初参加し、技術同人誌『RustではじめるOpenGL』を頒布させていただきました。 現在もPDF形式のダウンロード版と、紙版の両方をBOOTH.pmで販売させていただいてます。

toyamaguchi.booth.pm

ただし、紙版のほうは一部コンテンツが抜けていたことがあとから発覚しまして、別記事にて内容を通知させていただいております。

このようなコンテンツの抜け漏れがあった場合、PDF形式のダウンロード版ではすぐにアップデートをかけることができたのですが、紙版では対応が難しいところが難点です。

今回、私がAdvent Calendarのためにお伝えする内容は、このような抜け漏れ対策のためのものです。

コンテンツの抜け漏れ対策スクリプト

私がやらかしてしまったのは、章や節のタイトルは書いてあるのに、その中の本文を書き忘れた、というものです。 Re:Viewの記法でいえば、「= 章のキャプション」もしくは「== 節のキャプション」だけしか書いていなかったわけです。

印刷するまえに、より周到な確認ができていたらよかったのですが、さすがに一人で執筆していると気が回らないこともあります。 こんな状況下で書き忘れに気づくためには、ことあるごとに警告を出すようにするしかないと思いつきました。

そこでできあがったのが、コンテンツの抜け漏れ対策スクリプトです。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import yaml


def read_catalog_yaml():
    fp = open('articles/catalog.yml')
    yaml_data = yaml.load(fp)
    fp.close()

    file_name_list = list()
    for item in ['PREDEF', 'CHAPS', 'APPENDIX', 'POSTDEF']:
        if not isinstance(yaml_data[item], type(None)):
            file_name_list.extend(yaml_data[item])

    return file_name_list


def read_contents(file_name):
    contents_list = list()

    fp = open(os.path.join('articles', file_name))
    for line in fp:
        line = line.strip()
        if 0 == len(line):
            continue
        if line.startswith('=') and not line.startswith('===['):
            contents_list.append([line, 0])
        elif 0 != len(contents_list):
            contents_list[-1][1] = contents_list[-1][1] + 1
    fp.close()

    return contents_list


def main():
    file_name_list = read_catalog_yaml()
    contents_list = list()
    for file_name in file_name_list:
        contents_list.extend(read_contents(file_name))

    error_flag = False
    for contents in contents_list:
        if 0 == contents[1]:
            if False == error_flag:
                print('#########################################')
                print('# content checker found empty contents! #')
                print('#########################################')
                error_flag = True
            print('{} is empty'.format(contents[0]))

    if error_flag:
        exit(1)

    print('content checker found no issue!')
    exit(0)


if __name__ == '__main__':
    main()

解説

まずこのスクリプトはarticles/catalog.ymlを読み込み、すべての記事ファイルのリスト作ります。

そのあと、それぞれの記事ファイルを読み込み、各章・節の中に何行のコンテンツがあるのか数えていきます。 空行はカウントしないようにします。

すべてのファイルのコンテンツ(文字が存在する行の数)を数え上げ、まったくコンテンツを含まない章・節が存在した場合には、警告して終了ステータス1で終わります。 空コンテンツを発見したときの出力例はこちらです。

#########################################
# content checker found empty contents! #
#########################################
= はじめに is empty
= 著者紹介 is empty

問題なければ、その旨を表示して終了ステータス0で終わります。 空コンテンツがなかったときの出力例はこちらです。

content checker found no issue!

このアイデアは以前から持っていたのですが、Advent Calendarのために昨晩サクッと作りました。 もっとシンプルに書ける人は、自分で自作するとよいのではないでしょうか。 自作のツールはカスタマイズも楽ですし。

注意するところといえば、「=」が連続して始まる行が章・節の始まりですが、コラムの始まりと終わりは「===[column]」「===[/column]」と表せるようなので、その処理だけ区別しましょう。

まとめ

どうしても一人で書籍の執筆をしていると、執筆するほうに集中して、文章の確認作業がおろそかになったりします。 自分の代わりに随時内容をチェックしてくれる人がいたら、どれだけ助かることかと思います。

しかし、それもかなわない場合は、せめてあらかじめ予防線を張っておくことも大切です。 特にプログラムでのチェックは苦も無く確認してくれるので、ビルドするたびにチェックしたらよいと思います。

もしかすると技術同人誌を書いている多くの人が、文章校正ツールの「textlint」や、表記揺れのための「prh」を使っているかもしれません。 使っていない方は、後悔先に立たずですので、使うことを強くおすすめします。

ちなみに、この記事は私のやらかしちゃった話が元になっているわけですが、「本番環境でやらかしちゃった人 Advent Calendar 2019」というものもあって、興味深い記事もありますのでよろしければどうぞ。

技術同人誌の表紙をフリーソフトのGimpとImageMagickで作る

最近は、技術書典技術書同人誌博覧会などが定期的に開催されるようになったので、技術書同人誌を作って頒布したいかたも増えているようですね。

かく言う私も、技術書典7で書籍『RustではじめるOpenGL』を作成して、多くの人たちに頒布することができました。

技術同人誌を作るとき、本文の推敲に頭を悩ませることは多いのですが、本の体裁を整える上で、表紙を準備することも忘れがちですが大切な作業です。

たいてい表紙を作るときは、印刷所が指定するテンプレートをもとに作り始めていきますが、このファイル形式がPhotoshopのPSD形式だったり、EPS形式だったりすると、「やっぱりPhotoshopが必要なのか……」とPhotoshopの月額課金を始めることになったりします。

しかし、技術同人誌の表紙はフリーソフトで作れます!

現に私の書籍『RustではじめるOpenGL』も、フリーソフトで作りました。

f:id:toyamaguchi:20191116175326j:plain

本記事は、その方法を簡単に説明していきます。

ちなみに使用するソフトは、掲題の通り、GimpImageMagickを使っていきます。

テンプレートのダウンロード

私は印刷所として日光企画に印刷をお願いしました。バックアップ印刷所の一つなので、いろいろ特典もあります。

日光企画 - 【トンボダウンロード】オフセット用

Webページのタイトルには「オフセット用」と書いてありますが、オンデマンド印刷としてこのテンプレートを使わせていただきました。

B5版のEPS形式をダウンロードします。

テンプレートの読み込み

テンプレートの画像ファイルをGimpで開いてみましょう。 ここではEPSファイルをGimpに読み込ませてみます。

GimpはEPSファイルを読み込むと、その解像度を質問してきます。 というのも、EPSファイルは拡大縮小しても大丈夫なベクター形式だからです。 これを固定の画像サイズにするために解像度を入力します。

日光企画 - FAQよくある質問(オフセット編)

フルカラー原稿の場合は350dpiとなります。 色刷り原稿の場合、グレースケールは350dpi~600dpi、モノクロ2階調は600dpiを推奨しております。

この記述に従って、解像度を350に変更します。

f:id:toyamaguchi:20191116160807p:plain
解像度の変更

ちなみに、この数値の単位はppi (pixel per inch)のようで、dpi (dot per inch)と違います。 おそらく、ディスプレイにおける表示ピクセルと、印刷におけるドットの数という違いがあるのだと思いますが、大きな誤差はなさそうなので、これでいきます。

実際に印刷される領域を知る

本は、そのページ数によって背表紙の幅が決まります。

さきほどのトンボダウンロードのページの中に、「背幅の計算方法」について説明しているところがあります。

表紙込み総ページ数 × 本文用紙の厚さ = 背幅 (mm)

表紙込み総ページ数を、例えば100ページとします。 本文用紙の厚さは材質によって異なりますが、特に凝らなければ「上質紙90kg」というものになります。

これを踏まえて計算すると、

100枚 × 0.063mm/枚 = 6.3mm

だいたい6.3mm分です。

その場合は、この画像のように、中心から表紙側方向に約3mm、裏表紙方向に約3mm、合計約6mm程度の領域が背表紙になります。 (3mm目盛りが背表紙の領域の境界線です。)

f:id:toyamaguchi:20191116161701p:plain
背表紙の幅

また、そのほかの境界線をすべて考慮すると、次のような境界線が引けます。境界線をつけるまえと、つけたあとで比較してください。

f:id:toyamaguchi:20191116163014p:plain
テンプレートに境界線をつける前

f:id:toyamaguchi:20191116163105p:plain
テンプレートに境界線をつけたあと

これで、表紙の領域がわかりました。 あとは皆さんの好みの表紙を作っていけばOKです!

作業中の画像は、Gimpのファイル形式であるXCF形式で保存し、最終的に完成した際にはPNG形式でExportしました。

ImageMagickによるRGBからCMYKへの変換

通常、パソコンはRGB (レッド、グリーン、ブルー)の色要素によって、画面に画像を表示しています。 しかし、印刷業界ではCMYK (シアン、マゼンタ、イエロー、ブラック)の色要素を使って、インクを紙にくっつけて、印刷しています。 そのため、表紙の画像データもRGB形式からCMYK形式に変換する必要があります。

特にこの時点で、やむを得ずPhotoshopIllustratorの力を借りて、変換する人が多いのではないかと思います。 しかし、我々、ITエンジニアは便利な画像変換コマンドを知っているはず。

そう、ImageMagickです。

ImageMagickは、画像変換のための便利なコマンドがいくつか含まれたパッケージです。 今回はその中でも、convertコマンドによってRGBのPNG形式をCMYKのTIF形式に変換します。

convert before.png -colorspace CMYK after.tif

日光企画が推奨しているデータの保存形式のリストが、こちらのページにあります。

FAQよくある質問(オフセット編) - データの保存形式は何にすれば良いですか?

私はPhotoshopを持っていないので無理にPSD形式で保存せず、TIF形式でお願いしました。

初めての印刷だったので、実際に日光企画のお茶の水店に行って、データをお渡ししましたが、データ形式に関しては問題なかったようです。

RGBからCMYKに変換したことによる色の変化

このようにImageMagickを使うことで、簡単にCMYK形式の画像に変換することができましたが、実際に印刷するとモニタで見ていたときよりも少し暗い印象の仕上がりになることがあります。

これはImageMagickのせいというよりも、RGBからCMYKに変換したときによくある現象です。

この対策のために、少しだけ明るめに作っておくと良いです。 特にビビッドな赤や青の色を表紙のメインカラーに使っている場合は、要注意です。 「RGB CMYK変換」などでGoogle画像検索して、どの程度の変化があるのか確認してみましょう。

真っ黒な表紙についての補足

私の作った『RustではじめるOpenGL』は、真っ黒な表紙が特徴的です。

ImageMagickで画像変換したデータを日光企画に持っていったところ、受付のお姉さんに黒色の値に関しての注意を教えてもらいました。

RGBでの黒色の表現は(0, 0, 0)で、これをImageMagickで変換するとCYMKでの表現は(0, 0, 0, 255)となるようです。

このデータを印刷しても十分に黒いのですが、このデータよりももっと真っ黒になれる黒表現があるというのです。

それは、(100, 100, 100, 255)や、もしくは(255, 255, 255, 255)のような黒表現です。

実際にググってみると、CYMKのKだけで塗った黒をスミベタ、「C:40%, M:40%, Y:40%, K:100%」をリッチブラック、すべて100%にした4色ベタという種類があるそうです。

私は、日光企画の受付で(0, 0, 0, 255)の黒を即座に4色ベタに変換させるノウハウを持ち合わせていなかったので、そのまま入稿させていただきました。

ただ、実際にできあがったものを見てみても、「黒」としか認識できないくらい黒でした。

f:id:toyamaguchi:20191116175326j:plain
実際の表紙

特にこだわりがなければ、CYMKのKだけで塗ったスミベタの表紙でも問題なさそうです。

まとめ

このように、技術同人誌の表紙はフリーソフトでなんとかなります。

もしPhotoshopを使うことが高い障壁になっているかたがいたら、GimpImageMagickをおすすめします。

書籍『RustではじめるOpenGL』の内容漏れと更新のお知らせ

技術書典7で頒布いたしました『RustではじめるOpenGL』に、1ページ分程度の内容漏れが見つかりましたので、お知らせいたします。

該当箇所は、「第2章 SDL」の中にある「2.1.3 Windowsの場合」の内容が抜けておりました。

メインで利用しているPCがLinuxであったため、記事データがすべてLinux上にあったのですが、Windowsの内容を記事にする際に、いつもとは別ファイルに書いていたため、マージし忘れていたようです。

大変申し訳ありませんでした。

実際の内容

「2.1.3 Windowsの場合」では、マルチメディアライブラリである「SDL」をWindows環境で使えるように準備をします。

次の内容が、実際に書かれるはずであったものです。

2.1.3 Windowsの場合

Windowsでsdl2 crateを使うために、SDLのライブラリをインストールする必要があります。

このインストール方法については、上記のsdl2 crateのREADME.mdにも書かれていますが、本書でも解説していきます。

SDL公式サイトの中にあるダウンロードページ(https://www.libsdl.org/download-2.0.php)から、開発向けライブラリ(Development Libraries)のSDL2-devel-2.0.x-VC.zipをダウンロードします。

  • SDLの64bit向け開発ライブラリのあるディレクトリ:
    • SDL2-devel-2.0.x-VC\SDL2-2.0.x\lib\x64\

圧縮ファイルを展開したあと、上記の64bit向け開発ライブラリのあるディレクトリの中にあるファイルをすべてコピーし、次のディレクトリの中へペーストします。

  • ライブラリのインストール先:
    • C:\Users\USER_NAME\.rustup\toolchains\CURRENT_TOOLCHAIN\lib\rustlib\CURRENT_TOOLCHAIN\lib

上記の「USER_NAME」と書いてあるところには、あなたのアカウント名を入れてください。 また、「CURRENT_TOOLCHAIN」と書いてあるところが2個ありますが、現在利用しているRustのツールチェイン名を入れてください。

ツールチェインの調べ方は、「rustup show」コマンドで知ることができます。 64 bit Windowsで、Visual Studioを使っている場合、ツールチェインの名前は、x86_64-pc-windows-msvcになるはずです。

ファイルのコピーが完了したら、Windows上でRustからSDL2を使う準備ができました。

ダウンロード版コンテンツの更新

現在、BOOTHにて『RustではじめるOpenGL』のPDFのダウンロード(DL)版コンテンツを販売しております。

toyamaguchi.booth.pm

PDFファイルに関してはすでに内容を更新し、v1.1.0をアップロードいたしました。

すでにダウンロードした方も、新しいバージョンのPDFをダウンロードできるはずです。

紙版に関しては更新ができないため、このブログ記事を合わせて読んでいただけるとありがたいです。

改めて、本書に内容漏れがありましたことをお詫びいたします。