マルチプロセス+マルチスレッド同居の闇を試す!

f:id:masm11:20220413205641p:plain

こんにちは、masm11 です。

スレッドは皆さんご存知のことと思います。 並列処理をしたい場合によく使っていると思います。

プロセスの方があまり知られていないのかも知れません。 スレッドより昔からあるのですが…。 一つ以上のスレッドとメモリ空間等のリソースを合わせたもの、 とでも表現しましょうか。

どちらも、増やすことで並列処理をすることができます。 それぞれ、マルチスレッドやマルチプロセスと言われます。

マルチプロセスは、メモリ空間からして独立しているので、 完全に独立した処理を並列に実行したい場合によく使われます。 それに対してマルチスレッドはスレッドどうしが影響を与え合う 場合によく使われます。

しかし、マルチプロセスとマルチスレッドの共存は、 基本的にはご法度です。 理由は簡単で、デッドロックすることがあるからです。

今回は、その場面を見てみましょう。

コード説明

ではまず、今回使用するコードをお見せします。

#!/usr/bin/env python

import sys
import os
import threading
import time

mutex = threading.Lock()

def work1():
    while True:
        time.sleep(1)
        pid = os.fork()
        mutex.acquire()
        mutex.release()
        if pid == 0:
            sys.exit(0)

def work2():
    while True:
        mutex.acquire()
        time.sleep(0.01)
        mutex.release()

thread = threading.Thread(target=work2)
thread.start()

work1()

まずこのコードの終わりの方から説明します。

thread = threading.Thread(target=work2)
thread.start()

スレッドを一つ起動し、work2 関数を実行しています。

work1()

こちらは、現在のスレッドで work1 関数を実行しています。

つまり、2つのスレッドがそれぞれ work1 と work2 を実行する、 ということですね。マルチスレッドです。

work2 を見てみます。

    while True:

無限ループです。その中で何をやっているかと言うと、

        mutex.acquire()
        time.sleep(0.01)
        mutex.release()

mutex の獲得と解放を繰り返しています。 その中で sleep しているのは、別のスレッドがそのタイミングで 処理をしやすくするためです。

一方、work1 は何をしているかと言うと、

    while True:

こちらも無限ループです。その中で何をやっているかと言うと、

        time.sleep(1)

これは処理をゆっくりにするために入れてあります。 超高速でループを繰り返すと、わけがわからなくなりますので…

        pid = os.fork()

プロセスをコピー (fork) しています。 返り値は、子プロセス (コピー側) では 0 が返り、 親プロセス (コピー元) では 0 以外が返ります。

        mutex.acquire()
        mutex.release()

そして、mutex を獲得、解放しています。

        if pid == 0:
            sys.exit(0)

子プロセスの場合はそのまま終了しています。

結局たいしたことは何もしていません。 work2 側の acquire と release の間で、work1 側で fork してみて、 その後 mutex がちゃんと acquire できるか、を確認しています。

実行例

このプログラムを test-dl.py という名前で保存し、実行します。

luna:~ % python test-dl.py

何も出力はありません。

実行したまま、別端末を起動し、様子を見ます。

luna:~ % ps auxww | grep test-dl 
masm      222267  1.1  0.0  90660  8320 pts/1    Sl+  23:01   0:00 python test-dl.py
masm      222269  0.0  0.0  90660  5572 pts/1    S+   23:01   0:00 python test-dl.py
masm      222387  0.0  0.0  90660  5572 pts/1    S+   23:02   0:00 python test-dl.py
masm      222489  0.0  0.0  90660  5572 pts/1    S+   23:02   0:00 python test-dl.py
masm      222539  0.0  0.0   9044  2052 pts/2    S+   23:02   0:00 grep test-dl
luna:~ % 

何度も ps auxww | grep test-dl を実行すると、 ゆっくり増えていることが判ります。

プログラムをもう一度よく見てみましょう。 work1 で、親プロセスは無限にループしていますが、 子プロセスは必ず終了しています。 にも関わらず、プロセスが増えているのです。

何が考えられるでしょうか?

fork したけど exit までたどり着けていない。つまり acquire で待ち続けているのです。

デッドロックですね!

何故?

Linux では fork の際に現在のスレッドしかコピーされません。 子プロセスでは、他のスレッドが存在しないのです。 マニュアルにも記載があります。

子プロセスはシングルスレッドで生成される。つまり、 fork() を呼び出したスレッドとなる。

だから、work2 の acquire と release の間のタイミングで fork してしまうと、子プロセスでは mutex を release する者がおらず、 work1 側が acquire で待ち続けてしまうのです。

どう回避する?

上のページにヒントがあります。pthread_atfork です。ただし、それは C の話です。 python の場合は、os.register_at_fork を使います。

この関数を使うと、fork の前後 (後の場合は、親プロセスと子プロセスそれぞれで) に呼んで欲しい関数を指定できます。

具体的には以下のように使います。先程のコードからの差分だけ示します。

--- test-dl.py    2022-03-03 22:29:23.807615679 +0900
+++ test-dl-2.py    2022-04-13 20:28:08.081962854 +0900
@@ -22,6 +22,17 @@
         time.sleep(0.01)
         mutex.release()
 
+def acquire():
+    mutex.acquire()
+
+def release():
+    mutex.release()
+
+os.register_at_fork(
+    before = acquire,
+    after_in_parent = release,
+    after_in_child = release)
+
 thread = threading.Thread(target=work2)
 thread.start()

こうすることで、fork 前に mutex を acquire しておき、 work2 が mutex.acquire() するのを阻止することができます。 その状態で fork すると、 もちろん親プロセス・子プロセス両方でmutex は獲得されたままですので、 両プロセスで mutex.release() で解放してやります。

こちらを実行して数分後…

luna:~ % ps auxww | grep test-dl-2 
masm      439621  1.3  0.0  90600  7424 pts/1    Sl+  20:42   0:02 python test-dl-2.py
masm      440473  0.0  0.0   9044  1776 pts/2    S+   20:45   0:00 grep test-dl-2
luna:~ % 

増えてませんね。

まとめ

テストプログラムを提示し、デッドロックを発生させ、 理由と回避方法案を提示しました。

ただ、自分が書いたコードならこういう解決策がありますが、 python のモジュールに含まれるコードが対応しているとは限りません。 /usr/lib/python3.10/ 以下を grep してみたところ、いくつかの モジュールは対応してるようですね。

インゲージではまだまだエンジニアを募集しています。 詳細は以下のページへお願いします!

https://ingage.co.jp/recruit/

Alfred 4で特定のアプリだけが検索できなくなったときの対処法

こんにちは。 最近、M1 Mac に移行した @ishiyu です。

最近、M1 Mac 移行しました。
その際、なぜか Alfred でアプリケーションの検索結果が表示されなくなりました。
移行時は Dropbox で同期した設定ファイルを読み込んでいただけに、原因がまったく想像できません(T_T)

今までアプリケーションの起動はすべて Alfred 頼りだったので、今回はちゃんと表示するべく色々調べて試したことをここにまとめておきます。

どんな状態かを確認

石田の場合の症状としては、Google Chrome は検索結果として表示されるが、Firefox は表示されない状態でした。

f:id:ishiyu1125:20220413172607p:plain
google chrome は出る

f:id:ishiyu1125:20220413172831p:plain
firefox は表示されない

1. アプリケーションキャッシュのクリア

設定ファイルは同期しているんだし、どうせ、キャッシュがおかしいんだろ。
と思い、まずはアプリケーションキャッシュをクリアしてみました。

結果、何も変わりませんでした。。。

f:id:ishiyu1125:20220413173237p:plain
Alfred のアプリケーションキャッシュのクリア方法

2. MacOS metadata の Rebuild

もう原因が思いつきません。ここからは Google 先生頼りです。
そこで見つけた方法になります。

その1つ目が MacOS metadata の Rebuild になります。
キャッシュクリアの隣のボタンです。(気づけよ自分と思いましたが)

Reindex macOS rebuilds your Mac's low level file index. とあるので、インデックスが原因ならこれで直るはずです。

結果、何も変わりませんでした。。。

f:id:ishiyu1125:20220413173610p:plain
MacOS metadata の Rebuild

3. index 作成時の対象フォルダを見直す

こうなると、index 作成時の対象フォルダがおかしいだろうということで、フォルダを Reset してみました。

結果、何も変わりませんでした。。。

f:id:ishiyu1125:20220413174653p:plain
Alfred の検索フォルダをリセットする

4. spotlight の検索フォルダを見直す

MacOS metadata の Rebuild の情報を見つけて、 Alfred 本家のヘルプページを確認していた際に求めているページを発見!

www.alfredapp.com

上記ページから抜粋。

3. Check your Spotlight settings

Launch your System Preferences to the Spotlight preference pane to ensure all checkboxes for the 
file types you want to search are selected. Spotlight allows you to choose which file types are indexed 
by macOS. As Alfred relies on the same metadata, it's essential that the boxes are checked for all file 
types you want Alfred to find.

このページによると、Alfred の検索は spotlight で生成される検索インデックスに依存しているらしい。 spotlight でも検索フォルダはちゃんと選択しておけとのこと。

たしかに spotlight のインデックス生成はCPU喰うからすぐに全部OFFにしたなぁと思いながら確認すると、ビンゴでした。

f:id:ishiyu1125:20220413175624p:plain
firefox が検索できるようになった!

まとめ

最初から Alfred 本家のヘルプページを見ていれば、ここまで苦労しなかった気がします。 英語だからと邪険せずに、ちゃんと本家のページを見る癖付けをしましょう。

インゲージでは Alfred をフル活用できるエンジニアを募集しています。 詳細は以下からお願いします!

https://ingage.co.jp/recruit/

(もう去年の話だけど) Ruby 開発さんと一緒に勉強会しました

id:kizashi1122 です。

すいません。12月の話になります。

もう4ヶ月前の話ですが、弊社の開発パートナーである「Ruby開発」さんの開発メンバー3名の方に大阪本社にお越しいただきました。

普段はリモートで仕事をしていますが、このときは一緒にフェイス・トゥ・フェイスでお仕事をしました。

Ruby 開発さんには、インゲージで普段おこなっている社内勉強会に参加いただくこともありますが、このときは社内勉強会で登壇してもらいました。

f:id:kizashi1122:20220408182805p:plain

まずは文字コードの話をしてもらいました。エンジニアっぽいネタです。

f:id:kizashi1122:20220408182813p:plain

まさかのクリスマスネタ。非技術ネタです。

f:id:kizashi1122:20220408182827p:plain

一番印象に興味深かったのは「姫島について」でした。社会問題に切り込んだ素晴らしい発表でした。

※ちなみに、Ruby 開発さんは姫島に本社があります。

姫島オフィスの紹介 | Ruby開発

またいつか、インゲージの開発合宿@姫島の様子をお届けできればと思います。

では。

HackerRankをはじめよう!

こんにちは、ksr_cyclです。皆さんはデータ構造とアルゴリズムは好きですか?

私は普通です。好きになれるようにHackerRankで勉強をします。

HackerRankとは

HackerRank

多くのプログラミング問題に挑戦出来るサービスです。

また、スタートアップアクセラレーターである、Y-Combinatorが受け入れた最初のインド企業としても知られています。

使用できるプログラミング言語は、C, C++, Java, Ruby, Python, JavaScript, Rust 等、多くの選択肢があります。

問題は、

・アルゴリズム

・データ構造

・数学

・データベース

・正規表現

等、数多くのカテゴリから出題されており、企業のコーディングテスト対策、またコーディングテストのプラットホームとしても使われています。各問題に対して、ディスカッションスレッドが用意されており、多くのユーザーと問題に対しての議論ができます。

はじめかた

HackerRankのアカウント作成をするにあたって、サインアップします。 右の「For Job Seekers」を選択します。

f:id:ksr_cycl:20220405102343p:plain

アカウント作成が終わったら、今回は初級の問題を解いてみたいと思います。「Algorithms」を選択します。

f:id:ksr_cycl:20220405102927j:plain

問題を解いてみる

先頭に出ている「Solve Me First」を解いてみます。

f:id:ksr_cycl:20220405103407p:plain

クリックすると、このような画面が出てきます。

f:id:ksr_cycl:20220405103902p:plain

問題を読む

英文で書かれた問題文を読みます。この問題は簡単です。

f:id:ksr_cycl:20220405104531p:plain

問題文がなかなか理解が出来ない場合は、問題文の下にある

Sample InputSample OutputExplanationをじっくり読みます。

f:id:ksr_cycl:20220405103926p:plain

なんとなくでも問題のイメージが出来てくるはずです。

問題を解く

次に問題を解いていくので、使用する言語を選びます。私はPython3を選びます。

このように、すでに関数が書かれており、その中にソリューションを書いていきます。

この問題では、すでにヒントが書かれてますね。

f:id:ksr_cycl:20220405104048p:plain

問題が解けたら、下のRun Codeをクリックして、テストします。

f:id:ksr_cycl:20220405104829p:plain

f:id:ksr_cycl:20220405105325p:plain

動作は確認できました。

コードを提出する

問題を解けたら、コードを提出します。

Run Codeの隣にある、Submit Codeをクリックして、提出します。

f:id:ksr_cycl:20220405124556p:plain

再度、用意されたテストケースを通過したら、問題クリアです。

問題の難易度が上がるにつれて、この部分が非常に難しくなってきます。




f:id:ksr_cycl:20220405124652p:plain

この問題はクリアです。

もし問題を解くのに詰まったら、Disscussionsから質問もできます。

また、問題を解き終わったらLeaderBoardから自分の順位が確認出来ます。

その他

問題を解いていくと、ポイントが加算されていき、一定の量に達するとバッチがもらえます。

またこういった問題の他に、スキルを証明するCertifiedに挑戦出来ます。

制限時間以内に合格基準以上のスコアを出せれば、HackerRankから認定証がもらえます。

総括

1日1問を解く習慣が付けば、数か月ほどで結構な実力が付きそうです。

またHackerRankでは、スキル証明以外に、プログラミングコンテストの開催も出来ます。

以上、HackerRankの紹介でした。

FreeType を使って文字を描画する

こんにちは、masm11 です。

テキストエディタで1~2行めに以下のように書きます。

あいうえおかきくけこさしすせとたちつてと
abcdefghijklmnopqrstuvwxyzabcdefghijklmn

お使いのエディタにコピペしてみてください。 文字幅がきっちり 全角:半角=2:1 になっていれば、右端が揃うはずです。

最近、私の環境では揃わないんです… 何故なんでしょう… 今回はそんな疑問を調査してみました。

FreeType を使って描画する

Linux のアプリでは文字の描画に FreeType を使っていることが多いです。 さっそく FreeType を使ってみます。

こういったものはとっつきにくいので、チュートリアルを探しました。 以下にありました。

https://freetype.org/freetype2/docs/tutorial/step1.html

これを参考に作ったものが以下になります。 まずはコード全体をお見せした後、説明していきます。

#include <ft2build.h>
#include FT_FREETYPE_H

static unsigned int text[] = {
    0x00003042, 0x00003044, 0x00003046, 0x00003048,
    0x0000304a, 0x0000304b, 0x0000304d, 0x0000304f,
    0x00003051, 0x00003053, 0x00003055, 0x00003057,
    0x00003059, 0x0000305b, 0x0000305d, 0x0000305f,
    0x00003061, 0x00003064, 0x00003066, 0x00003068,
    0x0000000a,
    0x00000061, 0x00000062, 0x00000063, 0x00000064,
    0x00000065, 0x00000066, 0x00000067, 0x00000068,
    0x00000069, 0x0000006a, 0x0000006b, 0x0000006c,
    0x0000006d, 0x0000006e, 0x0000006f, 0x00000070,
    0x00000071, 0x00000072, 0x00000073, 0x00000074,
    0x00000075, 0x00000076, 0x00000077, 0x00000078,
    0x00000079, 0x0000007a,
    0x00000061, 0x00000062, 0x00000063, 0x00000064,
    0x00000065, 0x00000066, 0x00000067, 0x00000068,
    0x00000069, 0x0000006a, 0x0000006b, 0x0000006c,
    0x0000006d, 0x0000006e,
    0x0000000a,
};
#define LEN (sizeof text / sizeof text[0])

#define WIDTH  1024
#define HEIGHT 256
#define LEFT 16
#define TOP 128
static unsigned char paper[HEIGHT][WIDTH];


static void draw(FT_Bitmap *bitmap, int x, int y)
{
    for (int j = 0; j < bitmap->rows; j++) {
        for (int i = 0; i < bitmap->width; i++) {
            unsigned char c = bitmap->buffer[j * bitmap->pitch + i];
            if (c) {
                if (y + j >= 0 && y + j < HEIGHT) {
                    if (x + i >= 0 && x + i < WIDTH)
                        paper[y + j][x + i] = 255;
                }
            }
        }
    }
}

int main(void)
{
    FT_Library library;
    int error;

    error = FT_Init_FreeType(&library);
    if (error) {
        fprintf(stderr, "ft init error\n");
        exit(1);
    }

    FT_Face face;
    error = FT_New_Face(
            library,
            "/usr/share/fonts/TTF/NasuM-Regular-20141215.ttf",
            0,
            &face);
    if (error) {
        fprintf(stderr, "new face error\n");
        exit(1);
    }

    error = FT_Set_Char_Size(
            face,
            0,
            8 * 64,
            300,
            300);
    if (error) {
        fprintf(stderr, "set size error\n");
        exit(1);
    }
    
#define GETA(x) ((x) << 6)
#define UGETA(x) ((x) >> 6)
    FT_Vector pen;
    pen.x = GETA(LEFT);
    pen.y = GETA(TOP);
    unsigned int idces[LEN];
    for (int i = 0; i < LEN; i++) {
        if (text[i] == '\n') {
            fprintf(stderr, "%ld\n", pen.x);
            pen.x = GETA(LEFT);
            pen.y += GETA(32);
            continue;
        }
        
        idces[i] = FT_Get_Char_Index(face, text[i]);
        
        error = FT_Load_Glyph(face, idces[i], FT_LOAD_DEFAULT);
        if (error) {
            fprintf(stderr, "no glyph error\n");
            exit(1);
        }
        
        error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
        if (error) {
            fprintf(stderr, "glyph render error\n");
            exit(1);
        }
        
        draw(&face->glyph->bitmap,
                UGETA(pen.x) + face->glyph->bitmap_left,
                UGETA(pen.y) - face->glyph->bitmap_top);
        pen.x += face->glyph->advance.x;
    }
    fprintf(stderr, "\n");
    
    printf("P3\n%d %d\n%d\n", WIDTH, HEIGHT, 255);
    for (int y = 0; y < HEIGHT; y++) {
        for (int x = 0; x < WIDTH; x++) {
            if (paper[y][x]) {
                printf("%d %d %d ", 255, 255, 255);
            } else {
                printf("%d %d %d ", 0, 0, 0);
            }
            printf("\n");
        }
    }
}

さて、説明します。

#include <ft2build.h>
#include FT_FREETYPE_H

freetype のヘッダを include しています。 あまり見かけない使い方ですが、FT_FREETYPE_H はマクロです。

static unsigned int text[] = {
  // ...
};
#define LEN (sizeof text / sizeof text[0])

テキストを定義しています。前半がひらがな、後半がアルファベットです。 使いたい時に使いやすいように、unsigned int にしてあります。 また、LEN は文字数です。

#define WIDTH  1024
#define HEIGHT 256
#define LEFT 16
#define TOP 128
static unsigned char paper[HEIGHT][WIDTH];

WIDTH と HEIGHT は出力先エリアの幅と高さです。 paper が出力先エリアです。

LEFT と TOP は、文字を書き始める位置です。

static void draw(FT_Bitmap *bitmap, int x, int y)
{
  // ...
}

指定文字を paper の指定位置に描画する関数です。 文字は引数 bitmap にイメージとして格納されている前提です。 位置は引数 x, y に渡されてきます。

bitmap->buffer から1ドット読み取り、0 なら無視、0 でなければ paper 上でそのドットを塗りつぶしています。 paper はもともと 0 (黒) で塗りつぶされていて、そこに 255 (白) で描画します。 今回は灰色にはしません。

さて、ここから main 関数です。

    FT_Library library;
    int error;

これらの変数が必要なので定義しておきます。

    error = FT_Init_FreeType(&library);

freetype の初期化です。

    FT_Face face;
    error = FT_New_Face(
            library,
            "/usr/share/fonts/TTF/NasuM-Regular-20141215.ttf",
            0,
            &face);

フォントファイルを読み込んでいます。

    error = FT_Set_Char_Size(
            face,
            0,
            8 * 64,
            300,
            300);

文字のサイズをセットしています。 縦のサイズは 8ポイントです。

freetype では 26.6 の固定小数点が使われます。 26.6 とは、整数部を 26ビット、小数部を 6ビットで表現する、 という意味です。 イメージとしては、小数を 64倍して整数として扱う感じです。

1 は 64 という整数で扱い、1.5 は 96 という整数で扱います。 1 + 1.5 を計算したければ、64 + 96 で 160 です。加減算は そのまま計算できるので便利です。

#define GETA(x) ((x) << 6)
#define UGETA(x) ((x) >> 6)

<< 6 は64倍ですね。引数 x には整数を与えます。 GETA は「下駄を履かせる」的な意味で私が勝手に名付けました。

    FT_Vector pen;
    pen.x = GETA(LEFT);
    pen.y = GETA(TOP);

文字描画位置です。ここから始めます。

    unsigned int idces[LEN];

各文字は text[] に文字コードで与えられていますが、 フォントデータ中にアクセスするためには、 フォントデータ中のインデックスが必要です。 この配列にはそのインデックスを格納します。

ただ、配列でなくて良かった気はします。

    for (int i = 0; i < LEN; i++) {

ここから、各文字について処理します。

        if (text[i] == '\n') {
            fprintf(stderr, "%ld\n", pen.x);
            pen.x = GETA(LEFT);
            pen.y += GETA(32);
            continue;
        }

改行の場合の処理です。描画位置を次の行の左端に移動させています。 ここで、32 という改行幅は適当です。 今回は横幅にしか興味がないので、これで問題ありません。

        idces[i] = FT_Get_Char_Index(face, text[i]);

文字コードをフォントデータ中のインデックスに変換しています。

        error = FT_Load_Glyph(face, idces[i], FT_LOAD_DEFAULT);

その文字をロードします。

        error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);

それをイメージにします。

        draw(&face->glyph->bitmap,
                UGETA(pen.x) + face->glyph->bitmap_left,
                UGETA(pen.y) - face->glyph->bitmap_top);

先ほどの draw 関数で paper に描画します。 描画する際には、座標から下駄を外します。

        pen.x += face->glyph->advance.x;

描画位置を右へずらします。 どれだけずらすかは文字によります。

    printf("P3\n%d %d\n%d\n", WIDTH, HEIGHT, 255);
    for (int y = 0; y < HEIGHT; y++) {
      // ...
    }

画像として標準出力に出力します。

画像形式としては PNG, JPEG 等が有名ですが、扱いが面倒ですので、 簡単に出力できる PPM 形式を使います。Linux/UNIX では有名な形式で、 man ppm でマニュアルも読めます。

以上。たいしたことはしてなかったですよね。

  • 座標やサイズには 26.6 固定小数点を使う
  • 文字コードとインデックスは違う

チュートリアルを読みながら書けば、どうということはなかったです。

コンパイルする際には pkg-config を使います。

cc -g2 -O3 -Wall -o test `pkg-config --cflags --libs freetype2` main.c

こんな感じです。pkg-config --cflags --libs freetype2 って何? と思った方はこれだけを実行してみると良いです。

luna:ftsample % pkg-config --cflags --libs freetype2
-I/usr/include/freetype2 -I/usr/include/libpng16 -I/usr/include/harfbuzz -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include/sysprof-4 -pthread -lfreetype 

どこに include path を通して、 どこにある何ていうライブラリをリンクする必要があるかは、 freetype パッケージが知っていますので、それをそのまま指定している、という感じです。

そして実行して画像ビューワで見ると、以下のようになりました。

f:id:masm11:20220324003455p:plain

おぉ、揃ってない...

原因を探る

描画位置は face->glyph->advance.x ずつずらしていっただけですので、 この値を確認すれば良さそうです。

その結果、

  • 全角: 2112 ずつ
  • 半角: 1088 ずつ

となりました。64倍されててわかりにくいので、64 で割ります。

  • 全角: 33.0
  • 半角: 17.0

お? 半角2文字で 34ドットっていうことですから、全角1文字より 1ドット大きいですね。

なるほど、これが原因のようです。

linearHoriAdvance を使う

引き続き解決策を探します。 と言っても宛もないので、チュートリアルを引き続き読んでみます。 以下に2ページめがあります。

https://freetype.org/freetype2/docs/tutorial/step2.html

すると、linearHoriAdvance に出会いました。

horiAdvance (= advance.x) は整数 (= 26.6 なので 64 の倍数) に 丸められているけど、こちらは 16.16 固定小数点で、 丸める前の値が入っているそうです。

使ってみます。

プログラムの違いは以下の通りです。

--- 26-6.c      2022-02-23 22:44:07.635491014 +0900
+++ 16-16.c     2022-02-23 22:47:19.587834672 +0900
@@ -78,8 +78,8 @@
        exit(1);
     }
     
-#define GETA(x) ((x) << 6)
-#define UGETA(x) ((x) >> 6)
+#define GETA(x) ((x) << 16)
+#define UGETA(x) ((x) >> 16)
     FT_Vector pen;
     pen.x = GETA(LEFT);
     pen.y = GETA(TOP);
@@ -109,8 +109,8 @@
        draw(&face->glyph->bitmap,
                UGETA(pen.x) + face->glyph->bitmap_left,
                UGETA(pen.y) - face->glyph->bitmap_top);
-       fprintf(stderr, "%ld\n", face->glyph->advance.x);
-       pen.x += face->glyph->advance.x;
+       fprintf(stderr, "%ld\n", face->glyph->linearHoriAdvance);
+       pen.x += face->glyph->linearHoriAdvance;
     }
     fprintf(stderr, "\n");
     

これだけです。下駄のビット数と、描画位置の進め方だけですね。

これを実行した結果が以下になります。

f:id:masm11:20220324003512p:plain

横幅が揃いました!!

この時、描画位置がどれだけずつ進んでいたかというと、

  • 全角: 2184192
  • 半角: 1092096

16.16 なので 65536 で割ると、

  • 全角: 33.328125
  • 半角: 16.6640625

ぴったり、全角:半角=2:1 ですね!

そして、最初のプログラムで得た 33.0 と 17.0 が、 これの小数点以下を四捨五入したものっぽいこともわかりました。

まとめ

結局、 16.16 形式の linearHoriAdvance だと正確なのですが、 26.6 形式の advance.x には誤差が含まれていて、 そのため、全角:半角が綺麗に 2:1 になっていなかった、 ということでした。

でも、それが判ったからといって、だから何なのでしょう? 私のテストプログラムでは綺麗に揃えることができましたが、 結局エディタでは揃わないままです。

私は Emacs を使っています。Emacs では文字幅は整数で あることが前提のようです。 フォントの扱いを linearHoriAdvance に対応させたとしても、 結局、最終的に文字幅は整数ですので、誤差が発生してしまいます。 そして、Emacs を改造して文字幅を固定小数点にするのは (文字位置を固定小数点にするのも含め) 困難でしょう。 どちらかと言えば、フォントファイルをいじる方が可能性がありそうに思います。

…などと引き続き悩みつつ、今回はここまでにします。

インゲージでは引き続きエンジニアを募集しています。 詳細は以下からお願いします!

https://ingage.co.jp/recruit/

【ノンデザイナー向け】カッコイイデザインの資料を作る3つのコツ

f:id:mizutani1240:20220308184322p:plainこんにちわ!株式会社インゲージに2021年12月からデザイナーとして参画した水谷です。

私の方では主にデザインについて、様々な方に向けたTipsとなるような記事を書いていこうと考えております。

さて、今回のテーマはカッコイイ資料を作るコツとなります。

営業、マーケ、企画、エンジニア...どんな業種でも資料を作る機会はたくさんあると思います。 その際に、どうせならカッコイイ資料にしたくありませんか?

見た目のクオリティが高い資料は、聞き手の印象を上げるとともに、内容の信頼性の向上につながるはずです。

ここでお教えするコツを活かして、ぜひカッコイイ資料を作ってみてください!

コツ① フォントを変えてみる

フォントとは、文字のスタイルのことでPowerPointやGoogleスライド、Keynoteをはじめとしたプレゼンテーション資料作成ツールには必ずフォントを変更する機能が入っています。

デフォルトの状態だと、MSゴシックや游ゴシック体などが適用されており、これらで作ると「ありきたりな見た目」になってしまいがちです。

いつもと違うフォントで、かつ内容にあったものを選定し適用することで、雰囲気をガラッと変えることができます。

では、少し例を出して見てみましょう。

BeforeAfter

女性に向けた商品の資料ということ、そして化粧品という清楚感と高級感を感じさせるために、フォントをゴシックから明朝体に変更しました。

BeforeAfter

力強い背景ビジュアルに対して、デフォルトのフォントでは細く感じたため、IMPACTというフォントに変更して力強さを演出してみました。

このように、目的や意図に合わせてフォントを変えただけですが、これだけでかなり垢抜けた感じになるかと思います。

コツ② メリハリをつけてみる

全体を俯瞰し、色やテキストサイズのコントラスト(強弱)を意識してみましょう。

まずはこちらのベースをご覧ください。

デフォルト状態

Googleスライドのデフォルトのスタイルのまま作ればこのようになります。

まずは、タイトルテキストを大きく、逆に説明部分のテキストを小さくしてみましょう。

テキストサイズの変更

そして、説明部分のテキストは一段階落とした色(ダークグレー)に変更してみましょう。

説明テキストのカラーを1段階落とす

さらに、タイトルテキストの中でより目立たせたい文言にアクセントとなるカラーを設定し、さらに一段階テキストサイズを大きくしてみましょう。

タイトルテキストの一部を強調

最初の状態と見比べてみてください。パッとみて「何が言いたいのか」がわかりやすくなり、見た目のバランスも洗練されましたね。

非常に簡単な作業でこれだけ印象を変えることができるので、いかにコントラストが大事かということがわかります。

コツ③ 素材にこだわってみる

資料などに、挿絵を入れることは多々あるかと思います。

よくみられるのは「いらすとや」様のイラストですね。

「いらすとや」にはたくさんのイラストが存在し、非常に使いやすいのですが、あまりにもみなさんこちらを使用しているので目立たない印象となってしまいます。

こういった挿絵を使う際は、もう少ししっかり探してみましょう。

「〇〇 写真」や「〇〇 イラスト」というようにGoogleで検索したら、数々の素材サイトが出てきます。中には無料のものも多いので、そこから厳選しましょう。また、「〇〇 iillust」というように英語で表記すれば海外のサイトも参照することができます。

私がデザインや資料作成を行うときに参考にしたり、引用したりするイラストや写真の素材サイトをいくつか紹介します。

ACシリーズ(イラストAC / 写真AC 他)

イラストAC

www.ac-illust.com

www.photo-ac.com

こちらは基本登録無料で、数々のイラストや写真をダウンロードできる日本の素材サイトとなります。

イラストや写真だけでなくシルエットや動画など、たくさん展開されています。

Freepik

Freepik

jp.freepik.com

こちらは海外のサイトで、高クオリティでスマートなイラストや写真、テンプレートなどの素材が数多く掲載されており、ダウンロードできます。

基本は無料ですが、一部有料プラン向けのものもありますのでご注意ください。

この他にも、素材サイトはたくさんございますので、資料を作る際や少し手が空いた時に探してブックマークしておくと非常に便利です。

まとめ

いかがでしたでしょうか?

このようにちょっと工夫するだけで、どんな人でも簡単にカッコイイ資料を作ることができます。

ぜひこのテクニックを参考にしていただき、仕事に活かしていただけると私も嬉しいです!

json の中に含まれる json 文字列を jq で見やすくする

https://stedolan.github.io/jq/jq.png

id:kizashi1122 です。 今日はさらっと Tips を。

aws の ecr のライフサイクルポリシーをコマンドでみたいなと思ったときはこうすると思います。

aws ecr get-lifecycle-policy --repository-name <reponame>

すると json で返ってきます。これを jq でフォーマットするとしたら、

aws ecr get-lifecycle-policy --repository-name <reponame> | jq

とすればよいです。アウトプットは、

{
  "registryId": "<acccount id>",
  "repositoryName": "<reponame>",
  "lifecyclePolicyText": "{\"rules\":[{\"rulePriority\":1,\"description\":\"Keep last 30 images for build\",\"selection\":{\"tagStatus\":\"tagged\",\"tagPrefixList\":[\"foo-\"],\"countType\":\"imageCountMoreThan\",\"countNumber\":30},\"action\":{\"type\":\"expire\"}},{\"rulePriority\":2,\"description\":\"Keep last 30 images for released\",\"selection\":{\"tagStatus\":\"tagged\",\"tagPrefixList\":[\"bar-\"],\"countType\":\"imageCountMoreThan\",\"countNumber\":30},\"action\":{\"type\":\"expire\"}},{\"rulePriority\":3,\"description\":\"Keep last 10 images of untagged\",\"selection\":{\"tagStatus\":\"untagged\",\"countType\":\"imageCountMoreThan\",\"countNumber\":10},\"action\":{\"type\":\"expire\"}}]}",
  "lastEvaluatedAt": "2022-03-07T01:14:24+09:00"
}

こうなります。 lifecyclePolicyText は json の文字列ですがエスケープされまくりで見にくいです。このjsonをフォーマットして見たい場合もあると思います。
どうすればよいでしょう。

こうします。

aws ecr get-lifecycle-policy --repository-name <reponame> | jq '.lifecyclePolicyText' | jq '. | fromjson'

すると意図通り、

{
  "rules": [
    {
      "rulePriority": 1,
      "description": "Keep last 30 images for build",
      "selection": {
        "tagStatus": "tagged",
        "tagPrefixList": [
          "foo-"
        ],
        "countType": "imageCountMoreThan",
        "countNumber": 30
      },
      "action": {
        "type": "expire"
      }
    },
    {
      "rulePriority": 2,
      "description": "Keep last 30 images for released",
      "selection": {
        "tagStatus": "tagged",
        "tagPrefixList": [
          "bar-"
        ],
        "countType": "imageCountMoreThan",
        "countNumber": 30
      },
      "action": {
        "type": "expire"
      }
    },
    {
      "rulePriority": 3,
      "description": "Keep last 10 images of untagged",
      "selection": {
        "tagStatus": "untagged",
        "countType": "imageCountMoreThan",
        "countNumber": 10
      },
      "action": {
        "type": "expire"
      }
    }
  ]
}

のように表示されます。

見やすいですね!