本記事は、株式会社 函館ラボラトリが運営する「Bラボ」における、大人向け「看板アプリ(デジタルサイネージ)を作るコース」用教材テキストです。
- 【Bラボデジタルサイネージ1】Processing+ラズパイでデジタルサイネージを作る!
- 【Bラボデジタルサイネージ2】ラズパイ初期設定
- 【Bラボデジタルサイネージ3】準備プログラム作成&機能実現の方針決定
- 【Bラボデジタルサイネージ4】レイアウトの基準になるグリッドを表示する「GridModule」
- 【Bラボデジタルサイネージ5】レイアウトの基準になる枠を表示する「PlaceholderModule」
- 【Bラボデジタルサイネージ6】画像を全画面表示する「FullImageModule」
- 【Bラボデジタルサイネージ7】ページの自動切り替え
- 【Bラボデジタルサイネージ8】設置されている場所の名前を表示する「LocationModule」
- 【Bラボデジタルサイネージ9】現在の時間を表示する「DateModule」
- 【Bラボデジタルサイネージ10】ページ切り替えの時間が分かる「ProgressBarModule」
- 【Bラボデジタルサイネージ11】現在の表示中のページが分かる「PageControlModule」
- 【Bラボデジタルサイネージ12】現在の天気を表示する「WeatherRModule」(本記事)
- 【Bラボデジタルサイネージ13】直近2件のバス時刻表を表示する「BusRModule」
- 【Bラボデジタルサイネージ14】ごみ出しカレンダーを表示する「GomiRModule」
- 【Bラボデジタルサイネージ15】(発展編)ツイートを表示する「TwitterRModule」
- 【Bラボデジタルサイネージ16】開店/閉店を表示する「OpenCloseRModule」
- 【Bラボデジタルサイネージ17】部屋の温度を表示する「TemperatureRModule」
- 【Bラボデジタルサイネージ18】部屋の明るさを表示する「BrightnessRModule」
- 【Bラボデジタルサイネージ19】起動画面を表示する「LaunchingScreenModule」
- 【Bラボデジタルサイネージ20】RModuleの影を実装
前提
WeatherRModuleでは、天気情報を取得するために、OpenWeatherのAPIを使用します。
OpenWeatherのAPIを使用するためには、あらかじめOpenWeatherのアカウントを取得しておく必要があります。
作るもの
現在の天気を、OpenWeatherのAPIから取得して表示します。
背景画像の準備
WeatherRModuleの背景画像には、下の写真を使用します。
画像を保存し、「background.jpg」という名前をつけます。
(.png形式など別の形式の場合は、.jpg形式に変換してください。)
Processingの画面上のタブで、「スケッチ > スケッチフォルダーを開く」を選ぶと、開いている.pdeファイルが一覧で表示されます。
「data」フォルダの中に、新たに「weather」フォルダを作ってください。
先ほど名前をつけて保存した「background.jpg」を「data/weather」フォルダの直下においてください。(ドラッグ&ドロップ)
ここで新しく作った「data/weather」フォルダの位置を示すパスを、定数として定義します。
/* 略 */
final String AD_PATH = "ad/";
final String WEATHER_PATH = "weather/"; /* 追加 */
/* 略 */
WeatherRModule用関数の準備
新しいファイルRM_Weather.pde
を作ります。
WeatherRModuleの描画用関数drawWeatherRModule()
、天気データ更新用の関数updateWeather()
、APIアクセス用のURLを返す関数openWeatherURL()
を作ります。
void drawWeatherRModule(Area area) {
}
boolean updateWeather() {
return false;
}
String openWeatherURL(float latitude, float longitude, String apiKey) {
return "";
}
必要な変数の定義
WeatherRModuleで必要な変数について説明します。
OpenWeatherのAPIへアクセスするには、URLにいくつか値を埋め込む必要があります。
必要なものは下記です。
変数名 | 意味 | 使用場面 |
---|---|---|
LATITUDE |
緯度 | 天気を取得する地点の緯度を指定 |
LONGITUDE |
経度 | 天気を取得する地点の経度を指定 |
WEATHER_API_KEY |
OpenWeatherのAPIキー | データ取得時の認証のためURL内に埋め込み |
実際にURLの例を挙げると、下記のようになります(なお、ダミー値が入っているので、このURLのままではデータは取得できません)。
URLに使う値を、プログラム中に定義してみましょう。
緯度経度については、MIRAI BASEの建物のものを例とします。
/* 略 */
boolean isHoliday;
// APIを叩くときに使用する定数
final String WEATHER_API_KEY = ""; /* 追加 */
// WeatherRModule
final float LATITUDE = 41.81469; /* 追加 */
final float LONGITUDE = 140.75722; /* 追加 */
/* 略 */
天気を取得したい地点の緯度経度を知りたい場合は、Googleマップを使います。
Googleマップで地点を検索し、地点にカーソルを当てて右クリックします。
一番上の行に緯度と経度が表示されるので、これらの値でLATITUDE
とLONGITUDE
を初期化してください。
WEATHER_API_KEY
の値は、OpenWeatherのWebページから取得します。
取得してあるアカウントでログインしてください。
ログイン後、右上にアカウント名が表示されている部分をクリックし、「My API keys」を選択します。
遷移先のWebページで、「API Keys」タブをクリックすると、API keyが表示されている欄があります。
(下図では、API keyが表示されている部分は黒塗りしています。各ユーザごとに違う値が表示されています。)
API keyをコピーし、この値でWEATHER_API_KEY
を初期化してください。
final String WEATHER_API_KEY = "コピーしたAPI keyをここに入力";
次に、APIから取得した値を保存しておくための変数を定義します。
WeatherRModuleのスクリーンショットで、必要な値を説明します。
必要な値は以下の4つです。
変数名 | 意味 | 使用場面 |
---|---|---|
weatherIcon |
天気アイコン | 天気名に対応するアイコン |
temperature |
気温 | 現在の気温(摂氏) |
humidity |
湿度 | 現在の湿度 |
weatherString |
天気名 | 現在の天気を説明する文章 |
/* 略 */
// WeatherRModule
final float LATITUDE = 41.81469;
final float LONGITUDE = 140.75722;
PImage weatherIcon; /* 追加 */
float temperature = 0.0; /* 追加 */
int humidity = 0; /* 追加 */
String weatherString = ""; /* 追加 */
/* 略 */
変数を定義できたので、次は実際のデータを取得して入れてみます。
天気APIを叩く
まずは、天気APIを叩いてみましょう。
"https://api.openweathermap.org/data/2.5/onecall?lat=41.81469&lon=140.75722&units=metric&lang=ja&appid=XXXXXXXX" というURLの「XXXXXXXX」の部分を、自身で取得したAPI keyに置換してください。
置換できたら、ブラウザのURL欄に貼り付けてみてください。
正しく値が入力できていると、下図のような画面に遷移します。
見づらいですが、たくさんデータが返ってきています。
このときのURLを返す関数openWeatherURL()
を実装します。
/* 略 */
String openWeatherURL(float latitude, float longitude, String apiKey) {
/* return文を変更 */
return "https://api.openweathermap.org/data/2.5/onecall?" +
"lat=" + latitude + "&lon=" + longitude +
"&units=metric&lang=ja&appid=" + apiKey;
}
先ほど返ってきたデータをインデントや改行で整形してみたものが、下記のようになります。
{
"lat":41.8147,
"lon":140.7572,
"timezone":"Asia/Tokyo",
"timezone_offset":32400,
"current":{
"dt":1635609394,
"sunrise":1635628052,
"sunset":1635665610,
"temp":9.26,
"feels_like":9.26,
"pressure":1026,
"humidity":75,
"dew_point":5.06,
"uvi":0,
"clouds":100,
"visibility":10000,
"wind_speed":1.3,
"wind_deg":312,
"wind_gust":1.85,
"weather":[
{"id":804,"main":"Clouds","description":"厚い雲","icon":"04n"}
]},
"minutely":[
{"dt":1635609420,"precipitation":0},
/* 中略 */
{"dt":1635613020,"precipitation":0}
],
"hourly":[
{
"dt":1635606000,
"temp":9.22,
"feels_like":8.73,
"pressure":1026,
"humidity":74,
"dew_point":4.83,
"uvi":0,
"clouds":100,
"visibility":10000,
"wind_speed":1.53,
"wind_deg":300,
"wind_gust":2.07,
"weather":[
{"id":804,"main":"Clouds","description":"厚い雲","icon":"04n"}
],
"pop":0
},
/* 略 */
名前のついた値が、JSONという形式で返ってきています(Web APIでのデータのやりとりでよく使われるデータ構造です)。
得られたデータのうち、現在の天気を表すのは、"current"
という名前がついた括弧の中にある部分です。
current内のどの変数を参照すれば良いかを表にしてみます。
変数名 | 意味 | 使用場面 | JSON内の変数 |
---|---|---|---|
weatherIcon |
天気アイコン | 天気名に対応するアイコン |
"weather" 内の"icon"
|
temperature |
気温 | 現在の気温(摂氏) | "temp" |
humidity |
湿度 | 現在の湿度 | "humidity" |
weatherString |
天気名 | 現在の天気を説明する文章 |
"weather" 内の"description"
|
JSON形式のデータからこれらの値を取り出し、値を変数に代入する処理を、関数updateWeather()
で実装しましょう。
JSON形式のデータから値を得るには、すでに用意されている関数を使います。
URLを指定し、JSON形式のデータを得るには、下記の処理を用います。
(書かれているURLはダミーのURLです。)
String url = "https://example.blab.com"
processing.data.JSONObject json = loadJSONObject(url);
loadJSONObject()
で、括弧の中のURLから、JSON形式のデータを取得します。
取得したデータから、変数名を指定してJSONObjectを取得します。
変数名が"current"
のJSONObjectを取得するときは、下記の処理を用います。
processing.data.JSONObject current = json.getJSONObject("current");
複数のJSONObjectが入っている「JSONArray」を取得するときには、下記の処理を用います。
processing.data.JSONArray weather = current.getJSONArray("weather");
JSONObjectから型を指定して値を取り出すときは、下記の処理を用います。
processing.data.JSONObject example = json.getJSONObject("example");
int exInt = example.getInt("name");
float exFloat = example.getFloat("name");
String exString = example.getString("name");
getInt()
など、取り出したい値の型に応じて使い分けます。
APIから返ってきたデータのうち、湿度humidity
はint型、気温temperature
はfloat型、天気の説明description
はString型です。
JSONArrayから0番目のJSONObjectを取り出すときは、下記の処理を使います。
processing.data.JSONObject example = json.getJSONArray("example").getJSONObject(0);
これらの処理を使い、変数への代入まで実装します。
/* 略 */
boolean updateWeather() {
/* ここから追加 */
// 現在の天気を取得する
final String url = openWeatherURL(LATITUDE, LONGITUDE, WEATHER_API_KEY);
final processing.data.JSONObject json = loadJSONObject(url);
final processing.data.JSONObject current = json.getJSONObject("current");
final processing.data.JSONArray weather = current.getJSONArray("weather");
// 天気の説明、温度、湿度を取得
weatherString = weather.getJSONObject(0).getString("description");
temperature = current.getFloat("temp");
humidity = current.getInt("humidity");
/* ここまで追加 */
return false;
}
次に、天気に対応するアイコンを取得します。
JSONのデータのうち、"icon"
の値が"04n"
だとすると、アイコン画像のURLは次のようになります。
http://openweathermap.org/img/wn/04n@2x.png
URLからアイコンを読みこんで、変数に代入する処理を書きます。
/* 略 */
boolean updateWeather() {
// 現在の天気を取得する
final String url = openWeatherURL(LATITUDE, LONGITUDE, WEATHER_API_KEY);
final processing.data.JSONObject json = loadJSONObject(url);
final processing.data.JSONObject current = json.getJSONObject("current");
final processing.data.JSONArray weather = current.getJSONArray("weather");
// 天気の説明、温度、湿度を取得
weatherString = weather.getJSONObject(0).getString("description");
temperature = current.getFloat("temp");
humidity = current.getInt("humidity");
/* ここから追加 */
// 現在の天気に対応する画像を持ってくる
final String iconCode = weather.getJSONObject(0).getString("icon");
weatherIcon = loadImage("http://openweathermap.org/img/wn/" + iconCode + "@2x.png");
/* ここまで追加 */
return false;
}
APIから値が取得できなかったときの処理も実装します。
try-catch文で実装すると下記のようになります。
/* 略 */
boolean updateWeather() {
try {
// 現在の天気を取得する
final String url = openWeatherURL(LATITUDE, LONGITUDE, WEATHER_API_KEY);
final processing.data.JSONObject json = loadJSONObject(url);
final processing.data.JSONObject current = json.getJSONObject("current");
final processing.data.JSONArray weather = current.getJSONArray("weather");
// 天気の説明、温度、湿度を取得
weatherString = weather.getJSONObject(0).getString("description");
temperature = current.getFloat("temp");
humidity = current.getInt("humidity");
// 現在の天気に対応する画像を持ってくる
final String iconCode = weather.getJSONObject(0).getString("icon");
weatherIcon = loadImage("http://openweathermap.org/img/wn/" + iconCode + "@2x.png");
println("updateWeather(): 天気情報を取得しました。");
return true;
} catch (Exception e) {
println("updateWeather(): 天気情報を取得できませんでした。" + e);
return false;
}
}
関数updateWeather()
が実装できたので、この関数を呼び出すようにします。
天気データを更新するタイミングは、以下の2つとします。
- デジタルサイネージを起動したとき
- 1時間経過したとき
「デジタルサイネージを起動したとき」の処理は、関数initialize()
内に記述すれば実現できます。
void initialize() {
initializeDate();
initializeImage();
initializeGrid();
initializePlaceholder();
updateWeather(); /* 追加 */
}
/* 略 */
「1時間経過したとき」の処理は、関数updateDatas
内に記述すれば実現できます。
(記述箇所を間違えないでください。間違えると、APIへのアクセス回数が必要以上に増えてしまい、負荷を増やすことに繋がります。)
void updateDatas() {
updateDate();
final boolean isUpdatedDay = (day != beforeDay);
final boolean isUpdatedSecond = (second != beforeSecond);
if (isUpdatedDay) {
println("日付が変わりました。");
if (!updateIsHoliday()) {
println("ネットワークに接続できていない可能性があります。接続できているか確認してください。");
}
youbi = calcYoubi(year, month, day);
youbiString = youbiToString(youbi);
beforeDay = day;
}
if (isUpdatedSecond) {
if (second % STAY_SECOND == 0) {
updateNowPageID(true);
}
if (minute + second == 0) {
println(hour + "時になりました。");
updateWeather(); /* 追加 */
}
beforeSecond = second;
}
}
これで、天気データの取得処理が実装できました。
WeatherRModuleを描画
データが変数に代入できている状態になったので、画面上に表示するところまで実装します。
置換可能なReplaceableModuleを実装するとき、最低限定めなければいけないことは以下です。
- 描画時の基準となるエリア(Area.area1〜Area.area8のいずれか)
- 描画時のサイズ(Size.S〜Size.Lのいずれか)
まずはWeatherRModuleの描画時の基準となるエリアについて説明します。
WeatherRModuleの描画の基準になるのは、8つある表示エリアのうちArea.area1
です。
Area.area1
の左上の座標を取得します。
void drawWeatherRModule(Area area) {
int x = layoutGuideX(area); /* 追加 */
int y = layoutGuideY(area); /* 追加 */
}
/* 略 */
次に、描画時のサイズについて説明します。
PlaceholderModuleのときに説明したように、角丸四角2個分のサイズなら、Size.M
がWeatherRModuleのサイズになります。
このSize.M
という値から、描画エリアの幅と高さを取得する必要があります。
まずは、このようなReplaceableModule(RM)のサイズを返す関数を定義します。
関数を定義する前の準備として、RMに分類されるモジュールを列挙する列挙型変数を定義します。
/* 略 */
area7,
area8
}
/* ここから追加 */
enum RModule {
Weather
}
/* ここまで追加 */
/* 略 */
次に関数を作ります。
/* 略 */
/* ここから追加 */
Size moduleSize(RModule module) {
if (module == RModule.Weather) return Size.M;
return Size.S;
}
/* ここまで追加 */
定義したmoduleSize()
を使い、モジュールの幅と高さを取得します。
関数moduleWidth()
と関数moduleHeight()
を使います。
void drawWeatherRModule(Area area) {
RModule module = RModule.Weather; /* 追加 */
Size size = moduleSize(module); /* 追加 */
int x = layoutGuideX(area);
int y = layoutGuideY(area);
int w = moduleWidth(size); /* 追加 */
int h = moduleHeight(size); /* 追加 */
}
/* 略 */
ここまでで、描画の基準となる座標と、幅と高さが取得できました。
次に、文字や天気アイコンを表示します。
「現在の天気」「気象データ提供元: OpenWeather(TM)」、気温や湿度、天気と天気アイコンが必要です。
void drawWeatherRModule(Area area) {
RModule module = RModule.Weather;
Size size = moduleSize(module);
int x = layoutGuideX(area);
int y = layoutGuideY(area);
int w = moduleWidth(size);
int h = moduleHeight(size);
/* ここから追加 */
drawText(LEFT, BASELINE, BLACK_COLOR, 32, "現在の天気", x+50, y+50);
drawText(LEFT, BASELINE, BLACK_COLOR, 16, "気象データ提供元: OpenWeather(TM)", x+50, y+100);
drawText(LEFT, BASELINE, BLACK_COLOR, 64, int(temperature)+"℃ / "+humidity+"%", x+50, y+160);
drawText(LEFT, BASELINE, BLACK_COLOR, 42, weatherString, x+50, y+260);
image(weatherIcon, x+w-h, y+50, h, h);
/* ここまで追加 */
}
ここまで実装したら、drawModules()
内でdrawWeatherRModule()
を呼び出すように変更します。
void drawModules() {
if (nowPageID == 0) {
drawFullImageModule(background);
drawGridModule();
drawPlaceholderModule();
drawWeatherRModule(Area.area1); /* 追加 */
} else if (nowPageID == 1) {
drawFullImageModule(adImage[0]);
}
drawDateModule();
drawLocationModule();
drawProgressBarModule();
drawPageControlModule();
}
実行すると、下図のようになります。
データが正しく取得できなかったときの表示も実装します。
「データが正しく取得できたか」を示す変数を定義します。
/* 略 */
// WeatherRModule
final float LATITUDE = 41.81469;
final float LONGITUDE = 140.75722;
boolean isUpdatedWeather = false; /* 追加 */
PImage weatherIcon;
float temperature = 0.0;
int humidity = 0;
String weatherString = "";
/* 略 */
isUpdatedWeather
の値は、関数updateWeather()
が返した値を代入したものになります。
updateWeather()
を呼び出している箇所を変更します。
void initialize() {
initializeDate();
initializeImage();
initializeGrid();
initializePlaceholder();
isUpdatedWeather = updateWeather(); /* 変更 */
}
void updateDatas() {
updateDate();
final boolean isUpdatedDay = (day != beforeDay);
final boolean isUpdatedSecond = (second != beforeSecond);
if (isUpdatedDay) {
println("日付が変わりました。");
if (!updateIsHoliday()) {
println("ネットワークに接続できていない可能性があります。接続できているか確認してください。");
}
youbi = calcYoubi(year, month, day);
youbiString = youbiToString(youbi);
beforeDay = day;
}
if (isUpdatedSecond) {
if (second % STAY_SECOND == 0) {
updateNowPageID(true);
}
if (minute + second == 0) {
println(hour + "時になりました。");
isUpdatedWeather = updateWeather(); /* 変更 */
}
beforeSecond = second;
}
}
isUpdatedWeather
の値を更新できるようにしたので、isUpdatedWeather
がfalseのとき、データが取得できていないことを表示するように実装します。
void drawWeatherRModule(Area area) {
RModule module = RModule.Weather;
Size size = moduleSize(module);
int x = layoutGuideX(area);
int y = layoutGuideY(area);
int w = moduleWidth(size);
int h = moduleHeight(size);
drawText(LEFT, BASELINE, BLACK_COLOR, 32, "現在の天気", x+50, y+50);
drawText(LEFT, BASELINE, BLACK_COLOR, 16, "気象データ提供元: OpenWeather(TM)", x+50, y+100);
/* ここから変更 */
if (isUpdatedWeather) {
drawText(LEFT, BASELINE, BLACK_COLOR, 64, int(temperature)+"℃ / "+humidity+"%", x+50, y+160);
drawText(LEFT, BASELINE, BLACK_COLOR, 42, weatherString, x+50, y+260);
image(weatherIcon, x+w-h, y+50, h, h);
} else {
fill(0, 0, 0, 50);
noStroke();
rect(x, y, w, h, MODULE_RECT_ROUND);
drawText(CENTER, CENTER, WHITE_COLOR, 24, "WeatherModule\nデータを取得できません", x+w/2, y+h/2);
}
/* ここまで変更 */
}
ここでネットワークの接続を切ってみてください。
実行してみると、下図のようになります。
(表示が確認できたら、ネットワークに再接続してください。)
これで、データが取得できたときのデータ表示と、取得できなかったときの表示が実装できました。
次は、WeatherRModuleの背景を実装します。
モジュール背景の作成準備
下の背景画像を使い、
下のスクリーンショットのようなモジュール背景にします。
モジュールの背景は、GridModuleやPlaceholderModuleのときと同様に、PGraphicsを用いて実装します。
モジュール背景を保存しておく変数を用意します。
/* 略 */
// WeatherRModule
final float LATITUDE = 41.81469;
final float LONGITUDE = 140.75722;
boolean isUpdatedWeather = false;
PImage weatherIcon;
float temperature = 0.0;
int humidity = 0;
String weatherString = "";
PGraphics weatherBackground; /* 追加 */
/* 略 */
次に、weatherBackground
を生成するための処理を実装します。
weatherBackground
は、新しい関数initializeRModuleBackground()
内で初期化します。
/* 略 */
/* ここから追加 */
void initializeRModuleBackground() {
RModule module;
int w;
int h;
PImage back;
module = RModule.Weather;
w = moduleWidth( moduleSize(module) );
h = moduleHeight( moduleSize(module) );
back = loadImage(WEATHER_PATH + "background.jpg");
weatherBackground = createGraphics(w, h);
weatherBackground.beginDraw();
weatherBackground.colorMode(HSB, 360, 100, 100, 100);
weatherBackground.image( pImageCut(back, CENTER, CENTER, w, h) , 0, 0);
weatherBackground.fill(0, 0, 0, 40);
weatherBackground.noStroke();
weatherBackground.rect(0, 0, w, h);
weatherBackground.endDraw();
}
/* ここまで追加 */
/* 略 */
実行する前に、drawWeatherRModule()
内で描画するテキストの色をWHITE_COLOR
に統一します(BLACK_COLOR
のままだと、背景と同化して見づらくなってしまうため)。
void drawWeatherRModule(Area area) {
RModule module = RModule.Weather;
Size size = moduleSize(module);
int x = layoutGuideX(area);
int y = layoutGuideY(area);
int w = moduleWidth(size);
int h = moduleHeight(size);
drawText(LEFT, BASELINE, WHITE_COLOR, 32, "現在の天気", x+50, y+50); /* 変更 */
drawText(LEFT, BASELINE, WHITE_COLOR, 16, "気象データ提供元: OpenWeather(TM)", x+50, y+100); /* 変更 */
if (isUpdatedWeather) {
drawText(LEFT, BASELINE, WHITE_COLOR, 64, int(temperature)+"℃ / "+humidity+"%", x+50, y+160); /* 変更 */
drawText(LEFT, BASELINE, WHITE_COLOR, 42, weatherString, x+50, y+260); /* 変更 */
image(weatherIcon, x+w-h, y+50, h, h);
} else {
fill(0, 0, 0, 50);
noStroke();
rect(x, y, w, h, MODULE_RECT_ROUND);
drawText(CENTER, CENTER, WHITE_COLOR, 24, "WeatherRModule\nデータを取得できません", x+w/2, y+h/2);
}
}
/* 略 */
実行してみると、下図のようになります。
背景画像を切り取って表示できましたが、角が四角のままです。
角丸の画像を表示できるようにしたいので、PGraphicsのmask
を使用します。
PGraphicsのmask関数
PGraphicsのmask関数は、画像を図形で切り取るために使います。
例を示します。
下の図は、画像の上に白い角丸四角を重ねています。
この白い図形を使って、画像をマスクすると下図のようになります。
白い図形の形通りに画像が切り取られています。
出力に用いたプログラムは下記です。
size(900, 675);
PGraphics pg = createGraphics(900, 675);
pg.beginDraw();
pg.image(loadImage("background.jpg"), 0, 0);
pg.endDraw();
PGraphics mask = createGraphics(900, 675);
mask.beginDraw();
mask.noStroke();
mask.fill(255);
mask.rectMode(CENTER);
mask.rect(mask.width/2, mask.height/2, 845, 400, 30);
mask.endDraw();
pg.mask(mask);
image(pg, 0, 0);
プログラムのうち、
pg.mask(mask);
上記の記述で、画像を図形でマスクしています。
モジュール背景をマスクする
PGraphicsのmask関数で説明した時のように、モジュールのサイズに応じた白い図形を使い、画像をマスクします。
まず、「モジュールのサイズに応じた白い図形」を返すための関数sizeToModuleMask()
を実装します。
/* 略 */
PGraphics sizeToModuleMask(Size size) {
int w = moduleWidth(size);
int h = moduleHeight(size);
PGraphics moduleBackgroundMask = createGraphics(w, h);
moduleBackgroundMask.beginDraw();
moduleBackgroundMask.noStroke();
moduleBackgroundMask.fill(255);
moduleBackgroundMask.rect(0, 0, w, h, MODULE_RECT_ROUND);
moduleBackgroundMask.endDraw();
return moduleBackgroundMask;
}
/* 略 */
関数sizeToModuleMask()
を使って、モジュール背景を初期化する段階でマスクします。
/* 略 */
void initializeRModuleBackground() {
RModule module;
int w;
int h;
PImage back;
module = RModule.Weather;
w = moduleWidth( moduleSize(module) );
h = moduleHeight( moduleSize(module) );
back = loadImage(WEATHER_PATH + "background.jpg");
weatherBackground = createGraphics(w, h);
weatherBackground.beginDraw();
weatherBackground.colorMode(HSB, 360, 100, 100, 100);
weatherBackground.image( pImageCut(back, CENTER, CENTER, w, h) , 0, 0);
weatherBackground.fill(0, 0, 0, 40);
weatherBackground.noStroke();
weatherBackground.rect(0, 0, w, h);
weatherBackground.endDraw();
weatherBackground.mask( sizeToModuleMask( moduleSize(module) ) ); /* 追加 */
}
/* 略 */
初期化用のinitialize()
内で、initializeRModuleBackground()
を呼び出すようにします。
void initialize() {
initializeDate();
initializeImage();
initializeGrid();
initializePlaceholder();
initializeRModuleBackground(); /* 追加 */
isUpdatedWeather = updateWeather();
}
/* 略 */
最後に、背景を描画する処理を実装します。
void drawWeatherRModule(Area area) {
RModule module = RModule.Weather;
Size size = moduleSize(module);
int x = layoutGuideX(area);
int y = layoutGuideY(area);
int w = moduleWidth(size);
int h = moduleHeight(size);
image(weatherBackground, x, y, w, h); /* 追加 */
drawText(LEFT, BASELINE, WHITE_COLOR, 32, "現在の天気", x+50, y+50);
drawText(LEFT, BASELINE, WHITE_COLOR, 16, "気象データ提供元: OpenWeather(TM)", x+50, y+100);
if (isUpdatedWeather) {
drawText(LEFT, BASELINE, BLACK_COLOR, 64, int(temperature)+"℃ / "+humidity+"%", x+50, y+160);
drawText(LEFT, BASELINE, BLACK_COLOR, 42, weatherString, x+50, y+260);
image(weatherIcon, x+w-h, y+50, h, h);
} else {
fill(0, 0, 0, 50);
noStroke();
rect(x, y, w, h, MODULE_RECT_ROUND);
drawText(CENTER, CENTER, WHITE_COLOR, 24, "WeatherModule\nデータを取得できません", x+w/2, y+h/2);
}
}
ここまで実装できたら、実行して確認してください。
下図のように、モジュールの背景が角丸になっているはずです。
最後に
これで現在の天気を表示する「WeatherRModule」が実装できました。
次は、**直近2件のバス時刻表を表示する「BusRModule」**を作ってみましょう。