GA 版の画像モデルへ切り替えた直後、生成した壁紙が iPhone の画面にぴったり収まらないことに気づきました。preview モデルで詰めていた頃と同じプロンプトを投げているのに、上下に余白が出たり、逆に主題の端が切れたりします。原因は単純で、画像モデルは「1206×2622 ピクセルで出して」というピクセル指定を受け付けず、あらかじめ決められた縦横比のセットでしか返してくれないからです。
個人開発で壁紙アプリを長く運用していると、この「モデルが返す画像」と「端末が要求する解像度」の噛み合わなさは避けて通れません。しかも gemini-3.1-flash-image-preview と gemini-3-pro-image-preview は 6/25 に停止予定で、GA 版へ移したタイミングでちょうどこの問題に正面からぶつかりました。ここでは、私自身が壁紙の自動生成パイプラインで採っている「1枚のマスターから全デバイス分を切り出す」設計を、動くコードとともに共有します。
なぜピクセル指定が効かないのか
画像生成モデルに対して「1206×2622px で出力して」とプロンプトやパラメータで頼んでも、返ってくるのはモデルが対応している縦横比(1:1、3:4、4:3、9:16、16:9 など)のいずれかに丸められた画像です。これは GA 版でも preview 版でも変わりません。モデルは「比率」を理解しますが、「ピクセルの絶対値」をあなたの端末に合わせて出してはくれません。
一方で壁紙アプリが必要とするのは、端末ごとのピクセル絶対値です。主要な縦持ち端末を並べると、要求される解像度はおおむね次のようになります。
iPhone Air: 1260 × 2736 px(縦横比 約 0.46)
iPhone 17 Pro: 1206 × 2622 px(約 0.46)
iPhone 16/17 Pro Max: 1320 × 2868 px(約 0.46)
Pixel 9 Pro 級の Android: 1280 × 2856 px(約 0.448)
Android xxxhdpi の一般的な縦壁紙: 1440 × 3200 px(約 0.45)
注目したいのは、どれも縦横比が 0.45〜0.46 に集まっている点です。これは 9:16(= 0.5625)よりも縦長です。つまり 9:16 で生成した画像は端末が欲しい比率より「横に広い」ので、横方向を削れば全機種を賄えます。逆に言えば、生成側で 9:16 を選び、後処理で正確なピクセルへ落とし込むのが最も破綻しにくい方針になります。
設計の起点 — 1枚のマスターから全デバイスを切り出す
ここが本記事でいちばん伝えたい判断です。デバイスごとに生成 API を叩くのではなく、十分に大きい1枚のマスター画像を生成し、そこから各端末の解像度へクロップ・リサイズする 設計を私は推奨します。
理由は2つあります。1つはコストです。画像生成は1枚あたりの課金です。仮に1枚 $0.04 として、4機種ぶんを個別生成すれば1デザインあたり4回の課金が発生します。マスター1枚から切り出せば課金は1回で済み、生成回数は台数分の1、この例では 1/4 になります。壁紙を1,000デザイン用意するなら、4,000回が1,000回に減る計算です。
もう1つは一貫性です。端末ごとに生成すると、同じプロンプトでも構図や色がわずかにずれます。1枚のマスターから派生させれば、全機種で完全に同じ絵柄を配れます。
GA 版画像モデルでマスターを生成する
まずマスターを生成します。preview モデルからの移行は、基本的にはモデル ID の差し替えが起点です。GA 版では応答のフィールド名や対応縦横比が更新される場合があるため、切り替え後は必ず変更履歴で確認してから本番へ流してください。
from google import genai
from google.genai import types
client = genai.Client( api_key = "YOUR_GEMINI_API_KEY" )
# preview モデルは 6/25 に停止。GA 版のモデル ID へ切り替えます。
MODEL = "gemini-3.1-pro-image" # 旧: gemini-3-pro-image-preview
prompt = (
"A serene minimalist mountain range at dawn, soft gradient sky, "
"centered composition, generous empty space at top and bottom, "
"no text, no watermark, high detail"
)
resp = client.models.generate_content(
model = MODEL ,
contents = prompt,
config = types.GenerateContentConfig(
response_modalities = [ "IMAGE" ],
# 端末が要求する 0.46 より横に広い 9:16 で生成し、後処理で横を削る
image_config = types.ImageConfig( aspect_ratio = "9:16" ),
),
)
# 生成画像は inline_data に入って返ります
for part in resp.candidates[ 0 ].content.parts:
if part.inline_data:
with open ( "master.png" , "wb" ) as f:
f.write(part.inline_data.data)
break
プロンプトに generous empty space at top and bottom(上下に余白)と入れているのは、後でクロップしても主題が切れないようにするためです。この一文があるかないかで、量産時の歩留まりがはっきり変わりました。
端末解像度ちょうどに整える fit_to_device
マスターができたら、各端末の解像度へ正確に落とし込みます。ポイントは「カバー(cover)」方式です。レターボックス(余白)を出さないよう、対象サイズを必ず覆うところまで拡大してから中央でクロップします。
from PIL import Image
def fit_to_device (src_path: str , target_w: int , target_h: int , out_path: str ) -> str :
img = Image.open(src_path).convert( "RGB" )
sw, sh = img.size
# cover スケール: 縦横どちらも対象を覆う最小倍率を選ぶ(max を使うのが肝)
scale = max (target_w / sw, target_h / sh)
nw, nh = round (sw * scale), round (sh * scale)
img = img.resize((nw, nh), Image. LANCZOS ) # 縮小は LANCZOS で劣化を抑える
# 中央クロップで対象解像度へ
left = (nw - target_w) // 2
top = (nh - target_h) // 2
img = img.crop((left, top, left + target_w, top + target_h))
img.save(out_path, "PNG" )
return out_path
max() を使うのが要点です。min() にすると画像全体は収まりますが余白が出ます(contain 方式)。壁紙では余白は許されないので、必ず覆ってから余りを削る max() を選びます。
そしてもう1つ、マスターは全端末より大きく生成しておく ことが前提です。scale が 1 を超える(=拡大が必要)と、引き伸ばしでボケます。一番大きい 1440×3200 を確実に覆えるよう、マスターは 9:16 で長辺 3,000px 以上を狙うのが私の運用です。これなら全機種でスケールが 1 以下になり、常に縮小だけで済みます。
全デバイス分をまとめて書き出す
あとは端末テーブルを回すだけです。生成は1回、クロップは台数分です。
DEVICES = {
"iphone-air" : ( 1260 , 2736 ),
"iphone-17-pro" : ( 1206 , 2622 ),
"iphone-pro-max" : ( 1320 , 2868 ),
"pixel-9-pro" : ( 1280 , 2856 ),
"android-xxxhdpi" : ( 1440 , 3200 ),
}
import os
os.makedirs( "out" , exist_ok = True )
for name, (w, h) in DEVICES .items():
path = fit_to_device( "master.png" , w, h, f "out/ { name } .png" )
print ( f " { name } : { w } x { h } -> { path } " )
# 期待出力:
# iphone-air: 1260x2736 -> out/iphone-air.png
# iphone-17-pro: 1206x2622 -> out/iphone-17-pro.png
# ...
この構造なら、新機種が出ても DEVICES に1行足すだけで対応できます。生成 API には一切触れません。
セーフエリアを踏まえて主題が切れないようにする
クロップする以上、端は必ず削られます。問題は「どこが削られるか」を制御できているかです。私は中央 80% を主題のセーフエリアと定め、ロック画面の時計やウィジェットが乗る上部・下部には主題を置かないようにプロンプトで誘導しています。
生成物がセーフエリアを守れているかは、目視より重ね合わせ確認が早いです。簡単な検証用オーバーレイを用意しておくと、量産前のチェックが一気に楽になります。
from PIL import Image, ImageDraw
def draw_safe_area (path: str , out_path: str , safe: float = 0.8 ) -> None :
img = Image.open(path).convert( "RGB" )
w, h = img.size
draw = ImageDraw.Draw(img)
mx, my = w * ( 1 - safe) / 2 , h * ( 1 - safe) / 2
# 中央 80% の枠を赤で描画。主題がこの内側に収まっているか確認する
draw.rectangle([mx, my, w - mx, h - my], outline = ( 255 , 0 , 0 ), width = 6 )
img.save(out_path, "PNG" )
draw_safe_area( "out/iphone-pro-max.png" , "check/iphone-pro-max.png" )
この枠から主題がはみ出していたら、プロンプトの centered composition を強める、もしくはマスターを生成し直します。
preview から GA への移行で実際に確認したこと
6/25 の停止に向けた移行作業で、私が手元のパイプラインに対して確認した順序は次の通りです。
モデル ID を preview から GA(gemini-3.1-pro-image 等)へ差し替える
応答の inline_data の取り出しが変わっていないか、1枚だけ生成して確かめる
指定していた aspect_ratio が GA でも同じ値で通るかを確認する
生成失敗時に直近のマスターへフォールバックする経路を入れておく
4 を入れているのは、API 依存の自動処理は障害時に止まると痛いからです。生成が落ちても、前回のマスターを使い回して当日分の書き出しだけは完了できるようにしてあります。移行手順そのものの詳細はGA 版画像モデルへの移行手順 に、生成の再現性を上げる種値の扱いはseed パラメータで生成を再現する方法 にまとめてあります。
flash と pro、どちらの画像モデルで作るか
マスターをどちらの GA モデルで作るかは、用途で分けています。私の場合、シンプルなグラデーションや抽象パターンの壁紙は gemini-3.1-flash-image で十分です。安く速く、量産時のスループットが効きます。一方、細密な風景や質感が主役の絵柄は gemini-3.1-pro-image を選びます。同じプロンプトでも、縮小して端末解像度に落とし込んだあとのディテールの残り方が違うからです。
判断の目安はシンプルで、「長辺 3,000px へ縮小したときに細部が潰れて見えるか」です。潰れて見える題材だけ pro へ回し、それ以外は flash で回すと、コストと品質のバランスが取りやすくなります。すべてを pro で作ると、確かに綺麗ですが課金が跳ね上がります。
出力形式にも一言。生成直後は PNG で受け取って後処理しますが、アプリへ同梱する最終ファイルは WebP へ変換しています。同じ見た目でファイルサイズがおよそ 30% 前後小さくなり、アプリの容量とダウンロード時間に効いてきます。可逆性が要らない壁紙では、品質 90 前後の WebP が実用的な落としどころでした。
つまずきやすい5つの落とし穴
実際に運用してハマった、あるいは相談を受けたポイントを挙げます。
プロンプトでピクセル数を指定してしまう : 「1206×2622px で」と書いてもモデルは無視し、固定の縦横比で返します。サイズは後処理の責務だと割り切るのが正解です。
小さく生成して端末解像度へ拡大する : 必ずボケます。マスターは全端末より大きく生成し、縮小だけで賄ってください。
セーフエリアを考えずにクロップする : 主題が中央寄りでないと端が切れます。プロンプトで余白と中央配置を明示します。
デバイスごとに生成してコストを4倍払う : マスター1枚から切り出せば生成は1回です。生成回数は台数分の1になります。
Android の密度バケットを忘れる : 低密度端末向けにリソースが落ちる事故は、drawable-nodpi/ に高解像度版を1枚置いておく運用で回避しています。壁紙のような実画像は密度別に分割せず nodpi に集約するのが安全です。
App Store・Google Play の両方へ同じ絵柄の壁紙を配るとき、この5点を踏まえておくだけで差し戻しがかなり減りました。アーティストとして作った絵をそのまま全端末へ破綻なく届けられるのは、地味ですが日々の運用で効いてきます。
次の一歩
まずは手元の1枚に fit_to_device を通し、draw_safe_area で全端末ぶんに枠を重ねて、主題が切れていないかを目で確かめるところから始めてみてください。ここが安定すれば、あとは生成プロンプトの質を上げるだけで量産に乗せられます。お読みいただきありがとうございました。