画像RSS

ラベル 雑記 の投稿を表示しています。 すべての投稿を表示
ラベル 雑記 の投稿を表示しています。 すべての投稿を表示

2025年12月14日日曜日

C#でnoteの検索結果を全て取得する

noteで「禍話」に関する記事を全て取得するためにC#でプログラムを作成しました。noteのAPIの扱い方の参考になるかと思い、汎用的になるように加工して公開します。

noteでは、次のURLから検索結果をJSON形式で取得できます。

https://note.com/api/v3/searches?context=note&q=(検索ワード)&sort=(ソート方法)&size=(結果の数)&start=(開始番号)

各種のパラメータの意味は以下の通りです。

q=(検索ワード)
検索したい言葉。URL用にエンコードが必要です。「禍話」を検索する場合は「%E7%A6%8D%E8%A9%B1」となります。
sort=(ソート方法)
検索結果のソート方法。新しい順であれば「new」と書きます。
size=(結果の数)
検索結果の取得数。最大値は20のはずです。
start=(開始番号)
検索結果全体のうち、どこから取得するか。

次のプログラムは、noteの検索結果をすべて取得し、テキストファイルに出力します。 短期間で連続してnoteのAPIを叩くと、noteのサーバから怒られてしまいます。そのため、時間はかかりますが、Thread.Sleepを挟む必要があります。


using System.IO;
using System.Net.Http;
using System.Text.Json;
using System.Web;

// 待機時間生成用
var random = new Random();

// HTTPクライアント
using var httpClient = new HttpClient();

// note検索の基準値
int start = 0;

// startは20ずつ増加する
const int START_STEP = 20;

// 検索ワード
string? searchQuery = HttpUtility.UrlEncode("検索ワード");

// 出力先
using var sw = new StreamWriter("output.txt");

while(true)
{
    // 検索APIを呼び出す
    string noteUrlText = $@"https://note.com/api/v3/searches?context=note&q={searchQuery}&sort=new&size=20&start={start}";
    HttpResponseMessage httpResponseMessage = await httpClient.GetAsync(noteUrlText);

    Console.WriteLine(noteUrlText);

    // ステータスコードを出力
    Console.WriteLine($"Status Code: {httpResponseMessage.StatusCode}");

    // JSONデータを取得
    string? jsonContent = await httpResponseMessage.Content.ReadAsStringAsync();

    // noteの検索結果を解析する
    JsonDocument searchResult = JsonDocument.Parse(jsonContent);

    // note_cursor
    int cursor = 0;

    // dataエレメントがあるか確認
    if (searchResult.RootElement.TryGetProperty("data", out JsonElement dataElement))
    {
        // note_cursorエレメントを確認
        if (dataElement.TryGetProperty("note_cursor", out JsonElement noteCursorElement))
        {
            if (Int32.TryParse(noteCursorElement.GetString(), out int cursorConv))
            {
                cursor = cursorConv;
            }
        }
        else
        {
            // note_cursorエレメント取得失敗時はエラーと見なし、中断
            Console.WriteLine("失敗");

            break;

        }

        Console.WriteLine($"cursor: {cursor}");

        // notesエレメントを確認
        if (dataElement.TryGetProperty("notes", out JsonElement notesElement))
        {
            // contentsエレメントを確認
            if (notesElement.TryGetProperty("contents", out JsonElement contentsElement))
            {
                for (int i = 0; i < (cursor - start); i++)
                {
                    JsonElement contentElementChild = contentsElement[i];
                    string? authorId = null;

                    // 題名を取得
                    if (contentElementChild.TryGetProperty("name", out JsonElement nameElement))
                    {
                        sw.WriteLine($"題名\t{nameElement}");
                    }

                    // 記事投稿日時を取得
                    if (contentElementChild.TryGetProperty("publish_at", out JsonElement publishAtElement))
                    {
                        if (publishAtElement.TryGetDateTimeOffset(out DateTimeOffset dateTime))
                        {
                            sw.WriteLine($"投稿日時\t{dateTime.ToString("yyyy-MM-ddTHH:mm:sszzz")}");
                        }
                    }

                    // userエレメントを取得
                    if (contentElementChild.TryGetProperty("user", out JsonElement userElement))
                    {
                        // ユーザ名を取得
                        if (userElement.TryGetProperty("nickname", out JsonElement nicknameElement))
                        {
                            sw.WriteLine($"作者名\t{nicknameElement.ToString()}");
                        }

                        // ユーザのnoteページを取得
                        if (userElement.TryGetProperty("urlname", out JsonElement urlNameElement))
                        {
                            sw.WriteLine($"作者noteページ\thttps://note.com/{urlNameElement}");
                            authorId = urlNameElement.ToString();
                        }
                    }

                    // note記事のURLを取得
                    if (contentElementChild.TryGetProperty("key", out JsonElement keyElement))
                    {
                        sw.WriteLine($"noteのURL\thttps://note.com/{authorId}/n/{keyElement}");
                    }
                }
            }
        }
    }

    // cursorはstartから20増加する値になるはず
    if (cursor >= (start + START_STEP))
    {
        // 次のstartは20増加
        start = start + START_STEP;
    }
    else
    {
        // 20増加した値にならなかった場合、この周回で検索を終える
        break;
    }

    // 短期間でのnoteへのアクセスを防止するため、ランダム時間だけ待機
    Thread.Sleep(random.Next(10000, 15000));

    sw.WriteLine();
}

C#でBloggerページの自動更新

禍話リライトまとめを作る際に、Bloggerのページを自動で更新するプログラムが欲しくなり、Blogger APIを叩くプログラムを自作しました。

下準備: ページIDの取得

ページの自動更新のためには、ブログのIDとページのIDを知っている必要があるようです。

ブログのIDは簡単に確認できます。Bloggerのサイトを開いたときに、自動でリダイレクトされる先のURLを見れば解決します。URLは「https://www.blogger.com/blog/posts/(数字の羅列)」という形式であり、この数字の羅列がブログのIDです。

ただ、ページのIDは簡単には確認できないようでした。そのため、まずはページのIDを調べるプログラムを作成しました。

以下のプログラムは次の2点を予め実施する必要があります。

  • GoogleのAPIの認証のため、APIキーまたはOAuthクライアントIDを作成する (参考: アクセス認証情報を作成する)。作成したAPIキーまたはOAuthクライアントIDはすぐには有効にはならないため注意する。
  • NuGetパッケージで「Google.Apis.Blogger.v3」をインストールする。

このプログラムは、ページのキーを列挙し、そのID、題名、URLを出力します。 この例ではAPIキーを使用していますが、OAuthクライアントIDを使用しても良いです。どうせ必要になることを考えると、OAuthクライアントIDを作成した方が良いかもしれません。


using Google.Apis.Blogger.v3;
using Google.Apis.Services;

// Bloggerサービス
var service = new BloggerService(new BaseClientService.Initializer()
{
    ApiKey = "APIキー",
    ApplicationName = "アプリケーション名"
});

// リクエスト
var request = service.Pages.List("ブログのID");
request.MaxResults = 100;      // 結果の最大数

// レスポンス
var response = request.Execute();

// レスポンスを出力
using (var sw = new StreamWriter("output.txt"))
{
    foreach (var item in response.Items)
    {
        sw.WriteLine(item.Id);
        sw.WriteLine(item.Title);
        sw.WriteLine(item.Url);

        sw.WriteLine();
    }
}

本番: ページの更新

ようやく本題に入ります。以下のプログラムは次の2点を予め実施する必要があります。

  • GoogleのAPIの認証のため、OAuthクライアントIDを作成する (参考: アクセス認証情報を作成する)。“client_secret.json”をダウンロードしておくこと。作成したOAuthクライアントIDはすぐには有効にはならないため注意する。
  • NuGetパッケージで「Google.Apis.Blogger.v3」をインストールする。

このプログラムは、既存のBloggerのページの題名、内容を更新します。


using Google.Apis.Auth.OAuth2;
using Google.Apis.Blogger.v3;
using Google.Apis.Blogger.v3.Data;
using Google.Apis.Services;
using Google.Apis.Util.Store;
using static Google.Apis.Blogger.v3.Data.Page;

namespace BloggerPageUpdateProgram
{
    public class Program()
    {
        public static void Main()
        {
            new Program().Run().Wait();
        }
    
        private async Task Run()
        {
            // Googleの認証
            UserCredential credential;
            using (var stream = new FileStream("client_secret.jsonのパス", FileMode.Open, FileAccess.Read))
            {
                credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
                    GoogleClientSecrets.FromStream(stream).Secrets,
                    new[] { BloggerService.Scope.Blogger },
                    "user",
                    CancellationToken.None,
                    new FileDataStore(this.GetType().ToString())
                );
            }

            // Bloggerサービス
            var service = new BloggerService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = "アプリケーション名"
            });

            // Blogger接続用のデータ
            var blog = new BlogData();
            blog.Id = "ブログのID";
            
            // Bloggerのページへの書き込みデータ
            var page = new Page();
            page.Kind = "blogger#page";
            page.Blog = blog;
            page.Id = "ページのID";
            page.Title = "ページの題名";
            page.Content = "ページの内容";

            // リクエスト
            var request = service.Pages.Update(page, "ブログのID", "ページのID");

            // レスポンス
            var response = request.Execute();
        }
    }
}

2022年1月1日土曜日

パッケージ悪魔で振り返る真・女神転生V

『真・女神転生V』のパッケージ

 にようやく真・女神転生Vの全エンディングを制覇した。最初に難易度ノーマルで維持ルート、次に転生して変革ルート、再び転生して真・人間ルート、最後に新生して難易度もハードに変えて人間ルートを進んだ。一通り遊んだと思い、これを機に本作を振り返ってみようと思う。とはいえ、ただ感想を連ねるだけでは芸がない。そこで、パッケージに描かれた悪魔たちを元に話を進めていく。

幻魔アマノザコ

 本作の目玉の一つであるクエストナビを象徴するキャラクター。敵として出会うと厄介だが、仲魔としては弱点が多く、専用技もそれほど強くない。それでも、メインストーリーでもサブクエストでも出番に恵まれているため、不遇だとは思わない。これほど愛らしい娘 (こ) が無条件で好意を寄せているというのに、人間のためにと他の神魔ごと消し去ってしまうナホビノもいるらしい。人間のための選択ではあるが、まるで人間味がない。

 万古の神殿の鍵を回収するときに、アマノザコが何者かに乗っ取られて鍵の言葉を解説する場面がある。作中では特に説明が無いが、おそらくは事象ルシファーがアマノザコの口を借りたのだろう。どうしてそのようなことが可能だったのかは不明だが、「終の決戦」でルシファーが消滅した際に撒いた「種」がアマノザコの体内に入り込んでいるのかもしれない。アマノザコはアオガミの別個体の死骸に尸解の術を施すことで生まれた。材料の死骸に「種」が混入していたのだろうか。ところで、台東区 (第4のダアト) 攻略の冒頭で、アオガミが自身の根源的なところで使命感があるというような発言をしているが、これもルシファーの影響だろう。そもそも、アオガミが一時的に機能を止めたのも、ルシファーと遭遇した直後のことである。本作のストーリーでは、様々な場面でルシファーによる介入が見られる。

 日本語版のルシファーは三木眞一郎ボイスの含みのある演技である。しかし、英語版のプレイ動画を見る限りでは、英語版のルシファーは悪魔らしさのあるいかにも悪そうな声だった。少し残念である。

聖獣ハヤタロウ

 変革ルートを進むと、アブディエル討伐の手助けという名目でハヤタロウを渡され、合体が解禁される。しかし、そのタイミングでハヤタロウを渡されたところで、適正レベルが千代田区 (第3のダアト) 攻略の仲魔を役立たせるには愛が必要だ。ストーリー上で特に目立った活躍もなく、中田譲治ファンでもない限り、合体し直して魔導書をつぎ込むなんてことはしないだろう。そのような事情から、プレイヤーの間では「オソタロウ」という愛称で呼ばれ、本作のシナリオの犠牲者と見なされる。千代田区攻略の際、ユヅルはちょうど待機を命じられているため、その時点で主人公にハヤタロウを渡すという展開があっても良かったのだ。主人公の仲魔にならないにしても、主人公を手助けするイベントの一つでもあれば印象は変わっていただろう。

「みんなで発売日をカウントダウン!『真・女神転生V』発売前夜番組」より引用

 ハヤタロウに乗ってダアトを旅するという案もあったらしい。その案が実現していれば、このような悲しい扱いにはならなかっただろう。尤も、ハヤタロウでフィールドを高速で駆け抜けるという遊びを実現するには、かなり広い荒野のマップを用意する必要があるだろう。それを実現するには、BotW並の資本と開発力が求められるはずだ。下世話な話だが、今のアトラスには無理だろう。

 ただ、全くの役立たずというわけではない。耐性と自力で覚える技が優秀で、特に超人八雲ショウヘイ戦で大活躍する。ショウヘイは物理・電撃属性の強力な技を使用してくるが、ハヤタロウは「某を見よ」と耐性で攻撃を受け流しつつ、「物理ブロック」で味方を守ることができるのだ。新生して難易度ハードで遊んだときにかなり助かった。

 ストーリー上では目立たず、終盤で厄介払いのように押し付けられ、活躍させることもできるが新生を選ぶ必要がある。そんな悲しい役を背負わされたハヤタロウだが、元の飼い主の扱いも似たようなものだ。維持ルートのイチロウとアブディエルに比べると、変革ルートのユヅルは影が薄い。ユヅルは主役となるイベントがほとんど無く、ストーリー中で主張が変わることもない。ツクヨミの傀儡にされているようにすら見える。せっかく都合のいい妹がいるのだから、妹絡みのイベントで変革の必要性を認識するなんてことがあっても良かったのだが。

魔神コンス

コンスのサブクエストは設定こそよく出来ているものの、描写が伴っていないという印象だ。
仲魔にすると、セリフがいちいち格好良い。

 メインストーリーでは万魔会談だけが見せ場であり、以降はサブクエストにしか出番がない。サブクエストではかなり印象的な役を演じているが、フルボイスだったアマノザコイベントと比べると、声優の演技も演出も控え目だ。コンスとミヤズの関係性は設定としては面白そうなのだが、お出しされた本編はどうにも中途半端な量の描写で終わっている。特にミヤズはリビドーを沸き立たせる良いデザインだから残念だ。女神ミヤズ・ラーを召喚したり外道スライムと合体させたりしたかったとも思う。コンス・ラーの専用技が仲魔になった後には使用できないという点にも悲哀が感じられるが、この専用技は色々とややこしい仕様らしいから仕方がないのかもしれない。

 維持ルートの描写量と比べると、変革ルートは様々な面で控え目だ。もう一つダアトがあれば、ミヤズとコンス、エジプト王朝との関係性を軸に、ユヅルが日本は日本で、エジプトはエジプトでという多様性の素晴らしさに目覚め、可能性ある世界の創世を目指すようになる、というストーリーを作ることができたかもしれない。続編やメディアミックスで補足されるとありがたいのだが、期待しない方が良さそうだ。二次創作に期待した方がましかもしれない。無許可の二次創作なんてファン活動を謡ったところで所詮は海賊版に過ぎないが。

敦田ユヅルは妹思いという設定があるが、特に活かされてはいない。コンスのサブクエストがメインストーリーに絡んでいれば違ったのではなかろうか。
そうだね。

大天使アブディエル

 本作は維持ルートが優遇されている。ユヅルの影の薄さに比べると、イチロウはとにかく目立つ。そのうえ、第二形態まで用意されているのだ。天使が主の創りたもうた世界の維持のために、蛇に唆されて堕天し、デビルウーマンと化すという展開も面白い。ルート選択後に一緒に冒険ができたらなお良かったのだが、残念ながらルート選択後は雑な描写の末に退場する。

 維持ルートはまだましだが、本作はシナリオが非常に薄い。開発期間自体は長かったはずだが、開発の方向性が何度も変われば、いくら時間や予算があったところで足りなくなることもある。育成システムはよく出来ている。写せ身のおかげで主人公と仲魔にスキルや耐性を持たせるのも難しくない。戦闘システムは育成システムのおかげで理不尽すぎず、ほどよく歯ごたえのあるものとなっている。ダアトの探索も私としては苛々しつつも面白くもあった。悪魔のモデリングも手が込んでいる。ただ、そこまで頑張って力尽きてしまったのだろうか。

地母神ジョカ

得意技はデコピン。

 序盤では衝撃弱点を放置した主人公をバラバラにし、終盤ではプレイヤーを真・人間ルートへと誘う。存在感こそあるが、ショウヘイ周りも描写が薄く、ショウヘイの生い立ちをご親切にも説明する役目も押し付けられている。とはいえ、仲魔にすることもできないツクヨミと比べれば、扱いは遥かに良い。ショウヘイについて解説する場面は、ジョカがかなり面倒見のよい性格をしているという印象を与える効果があるとも言えなくもない。

 真・人間ルートへ進むと、あらゆる神魔が存在しない世界を創り直すことになる。クエストナビとともに荒野を旅し、フィン・マックールに永世の忠誠を誓ってもらい、コンスの決意とミヤズの願いを見届け、魅力的な仲魔たちとともに戦い抜いた先が、人間による人間のためだけの世界というのはどうかと思う。真・人間ルートを選ぶ義務は無く、皆がハッピーな結末にすればいいとも思わないが、最も難しいエンディングとしてお出しされるのがこのルートであると考えると、納得できないところがある。

 樹島サホリの末路、東京のダアト化に巻き込まれた人々の結末、学園襲撃の被害者たち、天使に消された知恵の持ち主のことを考えれば、神も悪魔も全て消すという選択自体は理解はできる。ただ、本作は人間よりも悪魔の描写の方が豊富だ。プレイヤーは冒険を進めるうちに、自然に悪魔への愛着が湧いてくる。会心波を連発してきた妖魔バルキリーや、鉄鋼針で主人公を針山に変えたアンズーは消滅させたくなるかもしれないが、全ての悪魔を消すという選択には抵抗感があるのが普通ではなかろうか。千代田区のコンクリートジャングルも、万古の神殿のFOE迷路も、すべてをエストマを唱えつつ駆け抜けたプレイヤーならば、アンズーさえも許すかもしれない。

 人間側の陣営の描写がもっとあれば、人間のための世界を作る気にもなるかもしれない。悪魔による人間への害をもっと強調する展開があっても違ったかもしれない。例えば、人間の遺体や遺品を冒涜する悪魔を退治するサブクエストなどがストーリー後半にあれば、悪魔を消すという選択も納得しやすくなるだろう。ラフムの一件の後は、悪魔の人類への脅威を感じさせるイベントがほとんどないため、悪魔との交流の方が印象に残りやすく、人間の未来のことはつい忘れがちになる。

合一神ナホビノ

 無口で無表情であり、プレイヤーの選択によって性格付けが決定される。自分の分身にしてもいいし、ロールプレイとして特定の性格になりきってもいい。避妊具についての知識がないピュアな少年にしてもいいし、知っているがすっとぼけているだけと考えてもいい。悪魔会話での不遜な物言いは、悪魔を言いくるめるための演技と考えてもいい。素で踊ったり歌ったり神だけに髪が長いと宣ったりしていると考えてもいい。私には万葉集を読む美少年になりきることはできなかったが、全くの他人になりきることもできなかった。

 本作はいくつか選択肢があるが、どれを選んでもその影響は維持ルートに寄るか、変革ルートに寄るかというだけだ。関係するのはルート分岐後の特典だけである。主人公がどのような思想の持ち主であっても、必ずラフムからタオを庇って致命傷を負う。サホリに引導を渡す場面は美しくはあるが、主人公にプレイヤーの意図から外れた人格があるように感じさせる。台東区でタオが主人公に協力するのは、サホリとの一件があったからという理由があるが、ラフムとの戦いの描写はルート分岐に関係する主人公の思想との食い合わせが悪いように思う。人間や秩序のためを思う主人公であれば、命にかえてタオを守ろうとしてもおかしくはないが、カオス寄りの思想であればどうだろうか。

悲しくも美しい場面だが、変革ルートを選ぶ主人公であれば、サホリの尊厳を尊重しすぎている気がする。

 本作に対する感想は、シナリオさえ良ければ、の一言である。登場人物や設定、舞台といった材料は良いのだが、ほとんど調理されないでお出しされているように思う。尤も、欠陥があるからこそ、語りたくなる意欲が湧くというところもある。欠点があるからこそ愛らしい、と言えなくもない。

2021年12月12日日曜日

真・女神転生Vの真Nエンディングを拝んで (ネタバレ注意)

『真・女神転生V』のパッケージ
狂気のデメテル。
サイン入りウツセミは2、3枚捨ててしまった。

 真・女神転生Vの発売から1か月が経過し、全ルートを巡った人も少なくない。のんびりと遊んでいた私は昨日になってようやくNeutralルートの真エンディングを拝んできた。それ以外ではLawルートのエンディングしか見ていない。難易度はNormalで固定。

 既に何度も言及されているだろうが、今作の残念なところは、人間の登場人物の掘り下げが浅いところだ。特に敦田兄妹はどうにかならなかったのだろうか。ミヤズはサブクエストの方が本番ではあろうが、そのサブクエストも物足りないというのが正直なところ。女神ミヤズをストックに入れたかったような気もする、というのは冗談だが。

 比較的に描写が多かった太宰イチロウも、Lawルートに進むとほとんど見せ場もなく退場してしまう。合一の余波で吹き飛ばされて死亡するという展開に、制作側の都合が垣間見える。せめて、真っ当に戦って真っ当に敗北してほしかった。

 人間の登場人物の描写をもっと多くしていれば、真Nエンディングを見たときの感想も少し変わっていたと思う。主人公は神魔を消し去ることを願い、アオガミとの切ない別れを経て、最後は唯一の神 (のような存在) として世界の行く末を見守る。そのような結末自体は良いのだが、本作では仲魔の育成や豊富なサブクエストの存在から、プレイヤーはむしろ神魔の存在を好ましく思うように誘導されると思う。少なくとも私はそうだった。ときには悪魔を殺し、ときには悪魔に殺され、ときには悪魔を貶め、ときには悪魔を愛好する。そんなゲームで神魔を消し去るのが真のエンディングですとお出しされても。せめて、人間側の描写がもう少し存在すれば、別の感想になったのではないかと思わずにはいられない。

 ただ、樹島サホリの末路や、東京のダアト化に巻き込まれて死亡した人々、学校で悪魔の襲撃にあった生徒たちのことを考えれば、神魔の存在を最初から無かったことにするのは正しい選択なのかもしれない。今回、蛇に乗せられて誘惑するロウヒーロー、生真面目なカオスヒーロー、傍目には狂人にしか見えないNルートの立役者といった、真・女神転生シリーズへの固定観念を逆手に取った構図を作り出そうとする意図が見える。真Nエンディングの大事なものをどこかに捨て去ったかのような後味も、制作側の意図したものなのかもしれない。

……それにしたって、描写が色々と足りていないと思うが。廃墟と化した東京の探索や、精巧な悪魔のグラフィック、興味深い設定と面白い要素は沢山あっただけに、本当にシナリオが惜しい。あと、女神ミヤズ使いたかった。そういえば、神魔のいない世界でもミヤズちゃんは健在だったけれど、コンスが存在しないのにどうして元気そうなのだろうか。主人公が気を利かせたのだろうか。

2017年6月12日月曜日

東方蛍光祭にて

来場者プレゼントの鞄

 2017年6月4日にて綿商会館で開催されたリグル・ナイトバグオンリーイベント「東方蛍光祭」に一般参加しました。実は、訳あって東方Projectに対する熱意が著しく磨耗していたのですが、リグルオンリーが開催されると聞いて急いで戻って参りました。今回は蛍光祭の感想などを書いていこうと思います。

 まずはイベント全体の雑感です。東方におけるリグルの扱いは、東方について詳しい方ならばご存知でしょう。正直なところ、マニアックなごく少数の人が来るだけなのではないかと思っていました。しかし、蓋を開けて見れば、参加者は思っていたよりも多く、カタログや来場者プレゼントも全て捌けたとのことでした。失礼な話ではありますが、私はその報告を聞いてかなり驚いたものです。サークル参加者にはリグル関係で名高い面々が集まり、コスプレイヤーも沢山いて至る所に緑髪の男女が歩くという状況で、リグルファンにとっては至上のイベントだったのではないでしょうか。現地でリグルのイラストやSSを制作してバトンを回すというリアル・リグルリレーという催しもなかなかに楽しいものでした。私も駄文を投げてきました。

 次いで頒布物についての感想です。私が以前から好いていたサークルはもちろんのこと、初めて名前を聞くような方も面白い創作物を出していて、様々な作品を入手することができました。今回は特に面白いと思ったものを紹介しようと思います。

 まずは立体物から紹介します。写真下はBIFRSTさんや水中花火さんが頒布していたアクリルキーホルダーです。素敵なイラストがそのままアクセサリーになるわけですから、アクリルキーホルダーというのは絵描きにとってもそのファンにとっても嬉しいものですね。 立体物の中で特に目を引いたのは「ぷちっと★くりえいと」というサークルが頒布していた粘土のフィギュア (写真下) です。このサークルの存在は知っていましたが、購入したのは今回が初めてです。小さくて可愛らしい作品で、持ち帰る際にはうっかり壊してしまわないかとヒヤヒヤしたものです。購入したときに破損防止の備えのついた容器に入れていただいたのですが、それでも繊細で愛らしいこのフィギュアに取り返しのつかない傷がつかないかと恐ろしくてたまりませんでした。

 次は同人誌の話です。最初は「月刊ナイトバグ PHAMTASM」。かつて刊行されていたリグルオンリーマガジンが今回のイベントのために復活したというもので、表紙は水中花火さんとうがつまつきさん。内容は様々な人が制作したリグルのイラストや漫画、SSが並んでいるというものです。月刊ナイトバグで見かけた面々が今回も寄稿しているのを見ると何か思うところがあります。この中での一押しは定宏さんの「いつか かえる ところ」というホラー漫画です。この本の中では唯一のホラーでして、私はホラーが好きなので特に好印象でした。有名な「渋谷のリグル・ナイトバグ」のように、リグルはホラーも似合うキャラクターだと思います。

 次は羊箱さんの「なつりぐる」。バカルテットで展開させる種類のギャグ漫画で、リグルが日焼けしたチルノのような人気を獲得するために卑怯な手段に出るという筋書きです。可愛い表紙につられて手が伸びて、表紙を開くとギャグと人気ネタという不安要素があって内心ヒヤリとしました。しかし、オチが見事の一言。買って良かったと思いました。

 次は粒状斑さんの「触角カタストロフ!!」。以前の「リグルは触角を自由に変えられる」という独自設定を再び採用した作品です。沢山のキャラクターが出てきてわちゃわちゃと騒ぐという種類の作品が好きな方におすすめです。斑さんは「月刊ナイトバグ PHAMTASM」にも成人向けにならない程度のアレを寄稿しているのでこちらも必見です。私としては、昔に描いていたシリアスなノリの作品をまた見たいと思っているところです。私が斑さんの存在を知り、リグルに対する認識を改めた契機となっている作品ですから。

 次はきじまさんの「リグルの合体技をかいた本。」。きじまさんと言えば、リグルだけでなく「ボクと魔王」の二次創作イラストをよく描いている方ですね。「グリモワール オブ マリサ」をオマージュした作品で、リグルが他のキャラクターと協力して放つスペルカードを魔理沙が記録するというものです。オマージュ元の方は遠い昔に読んだきりですが、そのときの魔理沙のやや嫌味のこもった解説が頭に浮かぶほどに上手にオマージュしているという印象でした。

 次は星落としさんの「ジオグラフィック」。簡素な線でリグルと蟲たちの一日を描いた漫画です。軽妙で味わいのある作品だと思います。スクリーントーンよりも線の表現が多いのが印象的で、それがすっきりとした印象を与えているというように感じました。結末も好きです。

 次はBIFRSTさんのイラスト本。内容はリグルに様々な蟲の格好をさせてみたというものです。BIFRSTさんはスラリとしたスマートな体つきのリグルをよく描く方ですが、硬い質感と鮮やかな模様をもつ蟲の表現と合わさり、蟲に興味のある方にとっては至福の一冊となっています。私にとって、今回のイベントで一位二位を争う収穫でした。ちなみに、相方さんのポストカードですが、封筒の出来が良すぎて未だに開封できていません。

 最後はうがつまつきさんの「なあ…交尾しようや…」。とんでもない題名ですが一般向けギャグ漫画です。題名通りの蟲の交尾についての面白漫画です。実を言うと、私はうがつさんのギャグ漫画を読んだことがなく、ギャグというものは色々と難しいものですから、この作品を購入したときは内心不安でした。ただそれは全くの杞憂。今思えば失礼と言わざるを得ないものでした。随所に小気味よく挟まれる小ネタに最後の渾身の大ネタ。リグルと蟲の生態を絡めたネタを使った作品は多くありますが、その中でもトップクラスの出来だと思いました。 今回のイベントで一ファンとしてうがつさんにお会いできたことも嬉しかったです。ところで「断罪のユディト」の読み切り版ってどうなりました?

 以上で感想は終わりです。東方オンリーイベントというだけでは今後参加しないと思いますが、リグルが主役のイベントが再度開かれるようなことがあれば、また参加したい所存です。関係者の更なる活躍を願っております。

……なんてことを書いていた最中に続報が。これは期待ですね。

2016年9月4日日曜日

グリフウィキで外字を使おう

グリフウィキの利用例
グリフウィキの利用例

 つい最近、知人から「グリフウィキ」 (GlyphWiki) なるウェブサイトの話を聞いた。グリフウィキとは「明朝体の漢字グリフ(漢字字形)を登録・管理し、皆で自由に共有することを目的としたウィキ」 (メインページ - GlyphWiki, 閲覧) だそうだ。誰でも自由に字形を作り、作った字形を利用することができる。 CSSのWebフォントを利用する機能を使えばウェブページ上で表示することも可能である。使いたいグリフを「グループ:全漢字」などのページや検索機能を使って探し出して、そのフォントのデータのURLを見つけてくればいい。

 グリフウィキの使い方や利用例の詳細は「グリフウィキで外字を使おう」に掲載している。興味のある方はどうぞ。

画像を出力するJava Servlet

 ウェブサーバに動的に画像を出力させたい場合がある。 リアルタイムで値が変動するデータのグラフを作りたい場合やアクセスカウンターを作りたい場合などがそれである。 グラフはJavaScriptのCanvas 2Dでも実現できなくもないだろうが、JavaScriptはクライアントに依存するため、それだけに頼るのは避けるべきである。SVGというXMLを利用した方法も使えるだろうが、SVGでは不都合な場合もあるだろう。SVGはブラウザによっては対応していないこともあるかもしれない。

 そのため、Java Servletを利用して画像を出力する方法を考えてみた。思いの外簡単であり、誰でも思いつくようなことだろうが一応書いておく。

 画像を出力するにあたってまず必要なことは、HttpServletインスタンスのdoGetメソッド等の引数である HttpServletResponseインスタンスのメソッドsetContentTypeを実行することである。 そのメソッドの引数に"image/png"などの画像の形式 (MIME型) を表す文字列を与えればいい。

 Javaで画像を扱うのに簡単な方法はBufferedImageを使うことである。 コンストラクタで一から生成してもいいし、ImageIOのreadメソッドで既存のファイルを鋳型として生成してもいい。BufferedImageの getGraphicsメソッドの返り値のGraphicsインスタンスや、 createGraphicsメソッドの返り値のGraphics2Dインスタンスを利用すれば、画像の変換等を行うことができる。

 画像の処理を行った後、最後にImageIOのwriteメソッドを実行する。第1の引数に処理を行ったBufferedImageのインスタンスを指定する。 第2の引数は"PNG"や"JPEG"等の画像の形式を表す文字列とする。 HttpServletResponseインスタンスのメソッドgetOutputStreamの返り値を第3の引数とする。 こうして、画像が出力される。

public class ImageDrawer extends HttpServlet {
 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  response.setContentType("image/png");
  
  BufferedImage img=new BufferedImage(200,200,BufferedImage.TYPE_INT_ARGB);
  Graphics g=img.getGraphics();

  g.setColor(Color.BLACK);
  g.fillRect(0,0,200,200);
  g.setColor(Color.WHITE);
  g.setFont(new Font("梅明朝",Font.BOLD,30));
  g.drawString("0123456789",0,30);
  g.drawLine(0,0,200,200);
  
  OutputStream os=response.getOutputStream();
  ImageIO.write(img,"PNG",os);
 }
}
実効結果

 上記のコードを実行すれば、このような画像が出力されるはずである。

Javaでスクリーンキャプチャ

Javaでスクリーンショットを撮る

 ディスプレイに表示されている画面を画像データ化したいとき、つまりはスクリーンショットを撮りたいときがある。WindowsではPrintScreenキーを押すとクリップボードにスクリーンショットを保存できる。画像編集ソフトウェアのGIMPには高度なスクリーンキャプチャの機能がある。 そして、Javaにもスクリーンショットのための機能がある。

 Javaには標準でjava.awt.Robotというクラスが存在する。このクラスは仕様書によると「Java プラットフォーム実装テストを自動化する」ために存在するらしく、自動的にキーボードのキーを押したりカーソルを移動させたりする機能がある。 当記事で紹介するのはこのクラスのcreateScreenCaptureメソッドである。

 このcreateScreenCaptureメソッドがスクリーンキャプチャの働きをするメソッドである。 引数はjava.awt.Rectangleのインスタンスであり、これで撮影する範囲をXY座標、幅、高さで指定する。 返り値はjava.awt.image.BufferedImageのインスタンスであり、このインスタンスは画像データを示す。こうして得られた画像をそのままファイルとして保存したり何かしらに表示したりすることができる。

連続スクリーンキャプチャ

 このスクリーンキャプチャの機能を利用するために私が実際に作成したプログラムの一部を次に示す。このプログラムは指定した回数countだけ撮影するというもので、指定したミリ秒delayの分だけ撮影毎に間隔をあける。そして、撮影したスクリーンショットのハードコピーをディレクトリdirに適当な名前をつけて保存する。 Robotにはスレッドスリープの機能をするdelayメソッドもあるためそれも利用した。 画像の保存ではjavax.imageio.ImageIOクラスを用いた。

//Robotを取得
Robot robot=null;
try{
 robot=new Robot();
}catch(AWTException e){
 e.printStackTrace();
}

for(int i=0;i<count;i++){
 /*遅延*/if(i!=0)robot.delay(delay);
 //ハードコピーを取得
 BufferedImage image=robot.createScreenCapture(rectangle);
 try {
  //画像を出力 (形式はPNG)
  ImageIO.write(image,"PNG",new File(dir,"screenshot"+(i+1)+".png"));
 }catch(IOException e){
  e.printStackTrace();
 }
}

 なお、RobotにはgetPixelColorというメソッドもある。このメソッドは指定した座標のピクセルの色を取得するというものである。 このように、Robotには少し変わった機能があり、工夫次第で面白い使い方もできるかもしれない。

JavaでWebカメラを制御する便利なAPI

 JavaでWebカメラを制御するためのAPIが存在し、「Webcam Capture in Java」で配布されている。 私がこのAPIの存在を知ったのは「Webcam Capture を使って Java から Webカメラ を簡単制御(キャプチャ・動体検知)する」という記事のおかげだが、使ってみると本当に簡単で便利だった。

 このAPIはJava6以降で動作し、当然ながら使用するにはWebカメラが必要である。ネットワークカメラも使えるらしい。「Webcam Capture in Java」によると、このAPIには次のような特徴があるという (下記の部分は前述のページを私が要約したものであり、参考にする際は自己責任でお願いします) 。

  • シンプルでスレッドセーフでノンブロッキングなAPI。
  • 他にソフトウェアを追加する必要なし。
  • マルチプラットフォーム。WindowsやLinux、Mac OSなどで動作する。また、様々なアーキテクチャをサポート。32ビット、64ビット、ARMで動作する。
  • 画像を備え付けのWebカメラから取得できる。USBで接続されたWebカメラも使える。ネットワークカメラも使用可能。
  • JMFやOpenCVなどの複数のキャプチャ用のフレームワークをサポート。
  • 一度コードを書いた後に別のドライバを使っても、そのドライバはWebcamDriverクラスによってラップされるため、コードを書き換える必要はない。
  • カメラ映像を表示するSwingコンポーネントが用意されている。複数のカメラから特定のカメラの映像を選んで表示できる。
なお、上記のサイトは全部英語で書かれている。正確な情報が欲しい方は頑張って和訳していただきたい。

 このAPIの使い方は「Webcam Capture を使って Java から Webカメラ を簡単制御(キャプチャ・動体検知)する」を読めばだいたい分かるはずである。 ここではその補足をする。詳細はAPI付属の仕様書を読んで把握してほしい。

 リアルタイムのカメラの映像を表示させたいときにはcom.github.sarxos.webcam.WebcamPanelクラスまたはcom.github.sarxos.webcam.WebcamViewerクラスのインスタンスを用いればいい。 WebcamPanelはjavax.swing.JPanelのサブクラスであり、WebcamViewerはjavax.swing.JFrameのサブクラスである。使い方はスーパークラスに準じる。 WebcamViewerではカメラの切り替えが可能である。 WebcamPanelは指定したWebカメラだけを対象にする。必要に応じて使い分けるといいだろう。

 また、Webカメラが取得できる画像の大きさはデバイスに依存するようである。com.github.sarxos.webcam.WebcamクラスのgetDeviceメソッドで com.github.sarxos.webcam.WebcamDeviceインターフェースというデバイスを抽象化したインターフェースが取得できる。 そのインターフェースのgetResolutionsメソッドで利用できる画像の大きさのリストを取得できる。 それから適当なものを選んで、WebcamクラスのsetViewSizeメソッドで取得したい画像のサイズを指定すればいい。 getResolutionsメソッドの返り値にないものを指定するとエラーが発生するため注意が必要である。