1 package charactermanaj.ui;
\r
3 import static java.lang.Math.ceil;
\r
5 import java.awt.AlphaComposite;
\r
6 import java.awt.Color;
\r
7 import java.awt.Composite;
\r
8 import java.awt.Graphics2D;
\r
9 import java.awt.GraphicsConfiguration;
\r
10 import java.awt.Image;
\r
11 import java.awt.Rectangle;
\r
12 import java.awt.Transparency;
\r
13 import java.awt.image.BufferedImage;
\r
14 import java.awt.image.VolatileImage;
\r
15 import java.beans.PropertyChangeListener;
\r
16 import java.beans.PropertyChangeSupport;
\r
17 import java.util.logging.Level;
\r
18 import java.util.logging.Logger;
\r
20 import charactermanaj.model.AppConfig;
\r
26 public class Wallpaper {
\r
31 private static final Logger logger = Logger.getLogger(Wallpaper.class.getName());
\r
34 * 壁紙の推奨されるブロックサイズ(幅).<br>
\r
35 * このサイズに満たない壁紙用画像は、このサイズに近い値まで敷き詰めて保持しておく.<br>
\r
37 private static final int wallpaperPreferredWidth = 128;
\r
40 * 壁紙の推奨されるブロックサイズ(高さ).<br>
\r
41 * このサイズに満たない壁紙用画像は、このサイズに近い値まで敷き詰めて保持しておく.<br>
\r
43 private static final int wallpaperPreferredHeight = 128;
\r
48 public static final String KEY_WALLPAPER_IMAGE = "wallpaperImage";
\r
53 private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
\r
58 private Color backgroundColor = Color.WHITE;
\r
63 private BufferedImage wallpaperImg;
\r
68 private float wallpaperAlpha = 1.f;
\r
71 * 壁紙用オフスクリーンサーフェイス.<br>
\r
74 private VolatileImage wallpaperVolatileImg;
\r
77 * 壁紙用オフスクリーンを生成したときに使用した背景色.<br>
\r
80 private Color wallpaperVolatileBgColor;
\r
84 * 壁紙が表示されない状態で壁紙イメージを構築する.<br>
\r
86 public Wallpaper() {
\r
91 * 壁紙の元画像を指定して壁紙イメージを構築する.<br>
\r
92 * nullを指定した場合は壁紙は表示されない.<br>
\r
93 * @param wallpaperImg 壁紙イメージ、もしくはnull
\r
95 public Wallpaper(BufferedImage wallpaperImg) {
\r
96 this.wallpaperImg = makeExpandedWallpaper(makeExpandedWallpaper(wallpaperImg));
\r
101 * nullの場合は解除される.<br>
\r
102 * 壁紙が小さい場合は推奨されるブロックサイズまで敷き詰めなおした状態で
\r
103 * 保持する.(描画不可軽減のため.)
\r
104 * したがって、{@link #getWallpaperImage()}を呼び出したときには
\r
105 * 拡張されたサイズとなっている.<br>
\r
106 * @param wallpaperImg
\r
108 public void setWallpaperImage(BufferedImage wallpaperImg) {
\r
109 // 現在のオフスクリーンを破棄する.
\r
110 disposeOffscreen();
\r
113 BufferedImage wallpaperImgOld = makeExpandedWallpaper(this.wallpaperImg);
\r
114 this.wallpaperImg = makeExpandedWallpaper(wallpaperImg);
\r
115 propertyChangeSupport.firePropertyChange("wallpaperImage", wallpaperImgOld, this.wallpaperImg);
\r
120 * 壁紙画像はブロックサイズまで拡張されたものとなっている.<br>
\r
121 * @return 壁紙画像、なければnull
\r
123 public BufferedImage getWallpaperImage() {
\r
124 return wallpaperImg;
\r
127 public float getWallpaperAlpha() {
\r
128 return wallpaperAlpha;
\r
132 * 壁紙画像を描画する場合のアルファ値を設定する.<br>
\r
133 * 負の値は0に、1以上は1に制限される.<br>
\r
134 * @param wallpaperAlpha アルファ値(0から1の範囲)
\r
136 public void setWallpaperAlpha(float wallpaperAlpha) {
\r
137 // 現在のオフスクリーンを破棄する.
\r
138 disposeOffscreen();
\r
141 if (wallpaperAlpha < 0) {
\r
142 wallpaperAlpha = 0;
\r
143 } else if (wallpaperAlpha > 1.f) {
\r
144 wallpaperAlpha = 1.f;
\r
148 float oldalpha = this.wallpaperAlpha;
\r
149 if (oldalpha != wallpaperAlpha) {
\r
150 this.wallpaperAlpha = wallpaperAlpha;
\r
151 propertyChangeSupport.firePropertyChange("wallpaperAlpha", oldalpha, wallpaperAlpha);
\r
155 public Color getBackgroundColor() {
\r
156 return backgroundColor;
\r
159 public void setBackgroundColor(Color backgroundColor) {
\r
160 // 現在のオフスクリーンを破棄する.
\r
161 disposeOffscreen();
\r
164 if (backgroundColor == null) {
\r
165 backgroundColor = Color.WHITE;
\r
169 Color colorOld = this.backgroundColor;
\r
170 if ( !colorOld.equals(backgroundColor)) {
\r
171 this.backgroundColor = backgroundColor;
\r
172 propertyChangeSupport.firePropertyChange("backgroundColor", colorOld, backgroundColor);
\r
177 * 壁紙を左上(0,0)を原点に指定した幅・高さでタイル状に敷き詰めて描画します.<br>
\r
178 * 壁紙が設定されていなければ何もしません.<br>
\r
179 * アプリケーション設定でオフスクリーンの使用が有効である場合、グラフィクスコンテキストに
\r
180 * あわせてオフスクリーンイメージをあらかじめキャッシュとして作成して転送する.<br>
\r
181 * オフスクリーンは初回描画時に構築され、以降、必要に応じて再作成される.<br>
\r
182 * オフスクリーンを即座に破棄する場合には{@link #disposeOffscreen()}を呼び出す.<br>
\r
184 * @param bgColor 背景色
\r
186 * @param h 高さ (画面高)
\r
188 public void drawWallpaper(Graphics2D g, int w, int h) {
\r
189 drawWallpaper(g, w, h, false);
\r
193 * 壁紙を左上(0,0)を原点に指定した幅・高さでタイル状に敷き詰めて描画します.<br>
\r
194 * 壁紙が設定されていなければ何もしません.<br>
\r
195 * アプリケーション設定でオフスクリーンの使用が有効である場合、グラフィクスコンテキストに
\r
196 * あわせてオフスクリーンイメージをあらかじめキャッシュとして作成して転送する.<br>
\r
197 * ただし、引数でオフスクリーンを使用しないと指定した場合はオフスクリーンには一切関知せず、
\r
198 * 通常の画像による背景描画を行う.<br>
\r
199 * (この場合、オフスクリーンは作成されず、現在あるものを再作成も破棄もしない.)
\r
201 * @param bgColor 背景色
\r
203 * @param h 高さ (画面高)
\r
204 * @param noUseOffscreen オフスクリーンを使用しない.(たとえ利用可能であっても)
\r
206 public void drawWallpaper(Graphics2D g, int w, int h, boolean noUseOffscreen) {
\r
208 Color bgColor = getBackgroundColor();
\r
212 if (wallpaperImg == null) {
\r
213 fillBackgroundColor(g, bgColor, w, h);
\r
217 if (wallpaperImg != null) {
\r
218 // オフスクリーンによる背景描画が行われたか?
\r
219 boolean drawOffscreen = false;
\r
221 if ( !noUseOffscreen) {
\r
222 // オフクリーンサーフェイスのチェックまたは生成
\r
223 AppConfig appConfig = AppConfig.getInstance();
\r
224 if (appConfig.isEnableOffscreenWallpaper()) {
\r
225 checkOrCreateOffscreen(g, bgColor, w, h);
\r
227 disposeOffscreen();
\r
230 // オフスクリーンフェイスが有効であれば、オフスクリーンで描画する.
\r
231 if (wallpaperVolatileImg != null) {
\r
232 int src_w = wallpaperVolatileImg.getWidth();
\r
233 int src_h = wallpaperVolatileImg.getHeight();
\r
234 drawWallpaper(g, w, h, wallpaperVolatileImg, src_w, src_h);
\r
236 // オフスクリーンがロストしていなければ
\r
237 // オフスクリーンで描画されたと判定する.
\r
238 drawOffscreen = !wallpaperVolatileImg.contentsLost();
\r
242 // オフスクリーンサーフェイスで描画されていなければ通常の画像で描画する.
\r
243 if ( !drawOffscreen) {
\r
244 if (wallpaperVolatileImg != null && logger.isLoggable(Level.INFO)) {
\r
245 logger.log(Level.INFO, "offscreen is lost.");
\r
248 fillBackgroundColor(g, bgColor, w, h);
\r
250 Composite oldcomp = g.getComposite();
\r
252 float alpha = getWallpaperAlpha();
\r
254 // アルファが100%の場合は、あえて設定しない.
\r
255 AlphaComposite comp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha);
\r
256 g.setComposite(comp);
\r
259 int src_w = wallpaperImg.getWidth();
\r
260 int src_h = wallpaperImg.getHeight();
\r
261 drawWallpaper(g, w, h, wallpaperImg, src_w, src_h);
\r
264 g.setComposite(oldcomp);
\r
271 * 現在使用しているオフスクリーン用ネイティブリソースを破棄する.<br>
\r
273 protected void disposeOffscreen() {
\r
274 if (wallpaperVolatileImg != null) {
\r
275 wallpaperVolatileImg.flush();
\r
276 wallpaperVolatileImg = null;
\r
278 if (logger.isLoggable(Level.FINER)) {
\r
279 logger.log(Level.FINER, "オフスクリーンを破棄しました。");
\r
285 * オフスクリーンイメージが有効であるかチェックし、
\r
286 * 有効でなければオフスクリーンを再作成、もしくは再描画する.<br>
\r
287 * オフスクリーンは背景画像と同じサイズで作成される.<br>
\r
288 * 背景画像が設定されていなければオフスクリーンも無効とする.<br>
\r
289 * @param g 実際のスクリーンデバイス(互換性あるオフスクリーンを作成するため)
\r
290 * @param bgColor 背景色
\r
291 * @param offscreen_max_w オフスクリーンの最大サイズ(幅)
\r
292 * @param offscreen_max_h オフスクリーンの最大サイズ(高さ)
\r
294 protected void checkOrCreateOffscreen(Graphics2D g, Color bgColor, int offscreen_max_w, int offscreen_max_h) {
\r
295 if (wallpaperImg == null) {
\r
296 // 壁紙もと画像がなければ何もしない.
\r
297 disposeOffscreen();
\r
301 GraphicsConfiguration gConf = g.getDeviceConfiguration();
\r
303 // オフスクリーンの状態を確認する.
\r
304 int validate = VolatileImage.IMAGE_INCOMPATIBLE;
\r
305 if (wallpaperVolatileImg != null) {
\r
306 validate = wallpaperVolatileImg.validate(gConf);
\r
307 if (logger.isLoggable(Level.FINEST)) {
\r
308 logger.log(Level.FINEST, "オフスクリーンの状態: " + validate);
\r
313 if (validate == VolatileImage.IMAGE_OK
\r
314 && (wallpaperVolatileBgColor != null && bgColor != null)) {
\r
315 if ( !wallpaperVolatileBgColor.equals(bgColor)) {
\r
316 validate = VolatileImage.IMAGE_RESTORED;
\r
321 int src_w = wallpaperImg.getWidth();
\r
322 int src_h = wallpaperImg.getHeight();
\r
325 // 要求された最大幅かアプリ設定の最大幅の小さいほうを最大幅とし、
\r
326 // それが壁紙もと画像よりも小さければ壁紙サイズと同じとする.
\r
327 AppConfig appConfig = AppConfig.getInstance();
\r
328 int offscreen_w = appConfig.getOffscreenWallpaperSize();
\r
329 int offscreen_h = appConfig.getOffscreenWallpaperSize();
\r
331 offscreen_w = Math.max(src_w, Math.min(offscreen_max_w, offscreen_w));
\r
332 offscreen_h = Math.max(src_h, Math.min(offscreen_max_h, offscreen_h));
\r
334 // ブロックサイズを満たすために必要な元サイズの繰り返し数
\r
335 int nx = (int) ceil((double) offscreen_w / src_w);
\r
336 int ny = (int) ceil((double) offscreen_h / src_h);
\r
338 // 繰り返し数からブロックサイズに近い元サイズで割り切れるサイズを求める
\r
339 offscreen_w = src_w * nx;
\r
340 offscreen_h = src_h * ny;
\r
342 // オフスクリーンが有効、もしくは描画が必要である状態の場合にサイズのチェックを行う.
\r
343 if (validate == VolatileImage.IMAGE_OK || validate == VolatileImage.IMAGE_RESTORED) {
\r
344 int currentOffW = Math.max(1, wallpaperVolatileImg.getWidth());
\r
345 int currentOffH = Math.max(1, wallpaperVolatileImg.getHeight());
\r
347 double ratioW = ((double) offscreen_w / currentOffW);
\r
348 double ratioH = ((double) offscreen_h / currentOffH);
\r
350 // オフスクリーンの描画済みサイズが要求サイズの2割を超えるか割り込んだ場合は
\r
352 if (ratioW < 0.8 || ratioW > 1.2 || ratioH < 0.8 || ratioH > 1.2) {
\r
353 validate = VolatileImage.IMAGE_INCOMPATIBLE;
\r
355 if (logger.isLoggable(Level.FINE)) {
\r
356 logger.log(Level.FINE, "オフスクリーンサイズの変更が必要です。: " + ratioW + "," + ratioH);
\r
361 // オフスクリーンの状態が再構築または再描画が必要であるか?
\r
362 if (validate != VolatileImage.IMAGE_OK ) {
\r
364 // オフスクリーンがないか、コンパチでなくなっている場合はオフスクリーンを生成する.
\r
365 if (wallpaperVolatileImg == null || validate == VolatileImage.IMAGE_INCOMPATIBLE) {
\r
366 // 現在使用しているネイティブリソースを破棄する.
\r
367 disposeOffscreen();
\r
369 // 新しいオフスクリーンサーフェイスを作成する.
\r
370 wallpaperVolatileImg = gConf.createCompatibleVolatileImage(
\r
371 offscreen_w, offscreen_h, Transparency.OPAQUE);
\r
373 if (wallpaperVolatileImg == null) {
\r
374 logger.log(Level.INFO, "オフスクリーンイメージは生成できません。");
\r
377 if (logger.isLoggable(Level.FINER)) {
\r
378 logger.log(Level.FINER, "オフスクリーンを構築しました。(サイズ:"
\r
379 + offscreen_w + "," + offscreen_h + ")");
\r
385 if (wallpaperVolatileImg != null) {
\r
386 if (logger.isLoggable(Level.FINER)) {
\r
387 logger.log(Level.FINER, "オフスクリーンの描画 (サイズ:"
\r
388 + offscreen_w + "," + offscreen_h + ")");
\r
391 Graphics2D vg = wallpaperVolatileImg.createGraphics();
\r
393 int ow = wallpaperVolatileImg.getWidth();
\r
394 int oh = wallpaperVolatileImg.getHeight();
\r
396 fillBackgroundColor(vg, bgColor, ow, oh);
\r
398 Composite oldcomp = vg.getComposite();
\r
400 float alpha = getWallpaperAlpha();
\r
402 // アルファが100%の場合は、あえて設定しない.
\r
403 AlphaComposite comp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha);
\r
404 vg.setComposite(comp);
\r
407 drawWallpaper(vg, ow, oh, wallpaperImg, src_w, src_h);
\r
410 vg.setComposite(oldcomp);
\r
417 wallpaperVolatileBgColor = bgColor;
\r
421 } catch (RuntimeException ex) {
\r
422 logger.log(Level.SEVERE, "オフスクリーンイメージの生成中に例外が発生しました。", ex);
\r
423 // 現在使用しているネイティブリソースを破棄する.
\r
424 disposeOffscreen();
\r
431 * @param bgColor 背景色、nullの場合は何もしない.
\r
435 protected void fillBackgroundColor(Graphics2D g, Color bgColor, int w, int h) {
\r
436 if (bgColor == null) {
\r
439 Color oldc = g.getColor();
\r
441 Rectangle clip = g.getClipBounds();
\r
442 if (clip == null) {
\r
443 clip = new Rectangle(0, 0, w, h);
\r
445 g.setColor(bgColor);
\r
454 * 壁紙を指定の領域まで敷き詰めて描画する.<br>
\r
456 * @param w 敷き詰めるサイズ(幅)
\r
457 * @param h 敷き詰めるサイズ(高さ)
\r
458 * @param wallpaperImg 敷き詰める画像
\r
459 * @param src_w 壁紙画像のサイズ
\r
460 * @param src_v 壁紙画像の高さ
\r
462 protected void drawWallpaper(Graphics2D g, int w, int h, Image wallpaperImg, int src_w, int src_h) {
\r
463 if (wallpaperImg == null) {
\r
467 // 表示範囲で表示できる壁紙を表示できる個数
\r
468 int nx = (int) ceil((double) w / src_w);
\r
469 int ny = (int) ceil((double) h / src_h);
\r
472 Rectangle clip = g.getClipBounds();
\r
474 // 描画対象領域にかかる壁紙を描画する.
\r
475 Rectangle rct = new Rectangle(0, 0, src_w, src_h);
\r
476 for (int iy = 0; iy <= ny; iy++) {
\r
477 for (int ix = 0; ix <= nx; ix++) {
\r
478 rct.x = ix * src_w;
\r
479 rct.y = iy * src_h;
\r
480 if (clip == null || clip.intersects(rct)) {
\r
484 rct.x + rct.width, rct.y + rct.height,
\r
495 * 壁紙を一定の大きさに敷き詰める.<br>
\r
496 * すでに十分大きい場合は何もしない.<br>
\r
497 * @param wallpaper 対象のイメージ
\r
498 * @return 拡張後のイメージ、もしくは同じイメージ
\r
500 protected BufferedImage makeExpandedWallpaper(BufferedImage wallpaper) {
\r
501 if (wallpaper == null) {
\r
506 int src_w = wallpaper.getWidth();
\r
507 int src_h = wallpaper.getHeight();
\r
509 // ブロックサイズよりも元サイズが大きければ何もしない.
\r
510 if (src_w > wallpaperPreferredWidth && src_h > wallpaperPreferredHeight) {
\r
514 // ブロックサイズを満たすために必要な元サイズの繰り返し数
\r
515 int nx = (int) ceil((double) wallpaperPreferredWidth / src_w);
\r
516 int ny = (int) ceil((double) wallpaperPreferredHeight / src_h);
\r
518 // 繰り返し数からブロックサイズに近い元サイズで割り切れるサイズを求める
\r
519 int w = src_w * nx;
\r
520 int h = src_h * ny;
\r
522 // ブロックサイズまで元画像を敷き詰める
\r
523 BufferedImage wallpaperNew = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
\r
524 Graphics2D g = wallpaperNew.createGraphics();
\r
526 drawWallpaper(g, w, h, wallpaper, src_w, src_h);
\r
532 return wallpaperNew;
\r
535 public void addPropertyChangeListener(PropertyChangeListener listener) {
\r
536 propertyChangeSupport.addPropertyChangeListener(listener);
\r
539 public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
\r
540 propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
\r
543 public void removePropertyChangeListener(PropertyChangeListener listener) {
\r
544 propertyChangeSupport.removePropertyChangeListener(listener);
\r
547 public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
\r
548 propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
\r