本記事は、株式会社 函館ラボラトリが運営する「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」**を作ってみましょう。















