1 package charactermanaj.graphics;
\r
3 import java.awt.Color;
\r
4 import java.awt.Dimension;
\r
5 import java.awt.Graphics2D;
\r
6 import java.awt.Rectangle;
\r
7 import java.awt.RenderingHints;
\r
8 import java.awt.geom.AffineTransform;
\r
9 import java.awt.image.AffineTransformOp;
\r
10 import java.awt.image.BufferedImage;
\r
11 import java.io.IOException;
\r
12 import java.util.ArrayList;
\r
13 import java.util.Arrays;
\r
14 import java.util.Collections;
\r
15 import java.util.List;
\r
16 import java.util.concurrent.Semaphore;
\r
17 import java.util.concurrent.TimeUnit;
\r
19 import charactermanaj.graphics.colormodel.ColorModel;
\r
20 import charactermanaj.graphics.colormodel.ColorModels;
\r
21 import charactermanaj.graphics.filters.ColorConvertParameter;
\r
22 import charactermanaj.graphics.io.ImageResource;
\r
23 import charactermanaj.graphics.io.LoadedImage;
\r
24 import charactermanaj.model.AppConfig;
\r
25 import charactermanaj.model.Layer;
\r
28 * 各パーツの各レイヤーごとの画像を色変換したのちレイヤーの順序に従い重ね合わせ合成する。
\r
32 public class ImageBuilder {
\r
37 private static final int MAX_TIMEOUT = 20; // Secs
\r
40 * 各パーツ情報を設定するためのインターフェイス.<br>
\r
41 * パーツ登録が完了したら、{@link #setComplite()}を呼び出す必要がある.<br>
\r
46 public interface ImageSourceCollector {
\r
54 void setSize(Dimension size);
\r
58 * 画像生成処理そのものは背景色を使用しないが、画像の生成完了と同じタイミングで背景色を変えるために ホルダーとして用いることを想定している.<br>
\r
62 void setImageBgColor(Color color);
\r
65 * アフィン変換処理のためのパラメータを指定する.<br>
\r
66 * 配列サイズは4または6でなければならない.<br>
\r
69 * パラメータ、変換しない場合はnull
\r
71 void setAffineTramsform(double[] param);
\r
75 * 複数パーツある場合は、これを繰り返し呼び出す.<br>
\r
76 * すべて呼び出したらsetCompliteを呼び出す.<br>
\r
80 * @param imageResource
\r
85 void setImageSource(Layer layer, ImageResource imageResource, ColorConvertParameter param);
\r
88 * パーツの登録が完了したことを通知する。
\r
94 * 合成が完了した画像を通知するインターフェイス
\r
98 public interface ImageOutput {
\r
105 Color getImageBgColor();
\r
112 BufferedImage getImageOutput();
\r
117 * イメージを構築するためのジョブ定義.<br>
\r
118 * イメージを構築するためのパーツを登録するハンドラと、合成されたイメージを取り出すハンドラ、および エラーハンドラからなる.<br>
\r
122 public interface ImageBuildJob {
\r
125 * 合成する、各パーツを登録するためのハンドラ.<br>
\r
130 void loadParts(ImageSourceCollector collector) throws IOException;
\r
133 * 合成されたイメージを取得するハンドラ
\r
136 * イメージを取得するためのインターフェイス
\r
138 void buildImage(ImageOutput output);
\r
146 void handleException(Throwable ex);
\r
154 private static final class BuildedPartsInfo {
\r
156 private final ImageBuildPartsInfo partsInfo;
\r
158 private final long lastModified;
\r
160 public BuildedPartsInfo(ImageBuildPartsInfo partsInfo, LoadedImage loadedImage) {
\r
161 this.partsInfo = partsInfo;
\r
162 this.lastModified = loadedImage.getLastModified();
\r
165 public ImageBuildPartsInfo getPartsInfo() {
\r
169 public long getLastModified() {
\r
170 return lastModified;
\r
176 * イメージ構築結果も格納される.<br>
\r
180 private static final class ImageBuildInfo {
\r
182 private ArrayList<ImageBuildPartsInfo> partsInfos = new ArrayList<ImageBuildPartsInfo>();
\r
184 private ArrayList<BuildedPartsInfo> buildPartsInfos = new ArrayList<BuildedPartsInfo>();
\r
186 private BufferedImage canvas;
\r
188 private Rectangle rct = new Rectangle(0, 0, 0, 0);
\r
190 private Color imageBgColor;
\r
192 private double[] affineParamHolder;
\r
194 private boolean sorted;
\r
197 public int hashCode() {
\r
198 return partsInfos.hashCode();
\r
202 public boolean equals(Object obj) {
\r
203 if (obj != null && obj instanceof ImageBuildInfo) {
\r
204 ImageBuildInfo other = (ImageBuildInfo) obj;
\r
206 if (!getPartsInfos().equals(other.getPartsInfos())) {
\r
207 // パーツ情報を重ね順をあわせて比較している.
\r
210 if (!rct.equals(other.rct)) {
\r
213 if (!(imageBgColor == null ? other.imageBgColor == null
\r
214 : imageBgColor.equals(other.imageBgColor))) {
\r
217 if (!(affineParamHolder == null ? other.affineParamHolder == null
\r
218 : Arrays.equals(affineParamHolder, other.affineParamHolder))) {
\r
227 * このイメージ構築情報と、すでに構築したイメージ情報を比較し、 同一であるか判定する.<br>
\r
228 * 引数に指定したイメージ構築情報が、まだ構築されていない場合は常にfalseとなる.<br>
\r
229 * イメージリソースが更新されていれば同一構成であってもfalseとなる.<br>
\r
232 * すでに構築済みのイメージ情報(結果が入っているもの)
\r
233 * @return 同一であればtrue、そうでなければfalse
\r
235 public boolean isAlreadyLoaded(ImageBuildInfo usedInfo) {
\r
236 if (usedInfo == null || usedInfo.getCanvas() == null) {
\r
239 if ( !usedInfo.equals(this)) {
\r
244 // 要求されているパーツ情報と、読み込み済みのパーツ情報が同一であるか判定する.
\r
245 int mx = partsInfos.size();
\r
246 int mxUsed = usedInfo.buildPartsInfos.size();
\r
247 if (mx != mxUsed) {
\r
248 return false; // 念のため
\r
250 for (int idx = 0; idx < mx; idx++) {
\r
251 ImageBuildPartsInfo partsInfo = partsInfos.get(idx);
\r
252 BuildedPartsInfo buildedPartsInfo = usedInfo.buildPartsInfos.get(idx);
\r
253 if ( !partsInfo.equals(buildedPartsInfo.getPartsInfo())) {
\r
254 // パーツ構成が一致しない.(念のため)
\r
257 long lastModified = partsInfo.getFile().lastModified();
\r
258 if (lastModified != buildedPartsInfo.getLastModified()) {
\r
268 * イメージ構築に使用したパーツ情報を記録する.
\r
272 * @param loadedImage
\r
275 public void addUsedPartsInfo(ImageBuildPartsInfo partsInfo, LoadedImage loadedImage) {
\r
276 buildPartsInfos.add(new BuildedPartsInfo(partsInfo, loadedImage));
\r
284 public BufferedImage getCanvas() {
\r
294 public void setCanvas(BufferedImage canvas) {
\r
295 this.canvas = canvas;
\r
298 public double[] getAffineParamHolder() {
\r
299 return affineParamHolder;
\r
302 public void setAffineParamHolder(double[] affineParamHolder) {
\r
303 this.affineParamHolder = affineParamHolder;
\r
306 public Color getImageBgColor() {
\r
307 return imageBgColor;
\r
310 public void setImageBgColor(Color imageBgColor) {
\r
311 this.imageBgColor = imageBgColor;
\r
314 public Rectangle getRct() {
\r
318 public void setRect(int w, int h) {
\r
324 * パーツのリストを取得する.<Br>
\r
325 * 取得された時点で、パーツ情報は重ね合わせ順にソートされている.<br>
\r
328 * @return パーツ情報のリスト
\r
330 public List<ImageBuildPartsInfo> getPartsInfos() {
\r
332 Collections.sort(partsInfos);
\r
335 return Collections.unmodifiableList(partsInfos);
\r
338 public void add(ImageBuildPartsInfo imageBuildPartsInfo) {
\r
340 partsInfos.add(imageBuildPartsInfo);
\r
343 public int getPartsCount() {
\r
344 return partsInfos.size();
\r
352 private ColorConvertedImageCachedLoader imageLoader;
\r
355 * 最後に使用したイメージビルド情報.(初回ならばnull)
\r
357 private ImageBuildInfo lastUsedImageBuildInfo;
\r
360 * イメージのローダーを指定して構築します.<br>
\r
362 * @param imageLoader
\r
365 public ImageBuilder(ColorConvertedImageCachedLoader imageLoader) {
\r
366 if (imageLoader == null) {
\r
367 throw new IllegalArgumentException();
\r
369 this.imageLoader = imageLoader;
\r
374 * イメージビルドジョブより、構築すべきイメージの情報を取得する.
\r
376 * @param imageBuildJob
\r
378 * @return 取得されたイメージ構築情報
\r
379 * @throws IOException
\r
381 * @throws InterruptedException
\r
384 protected ImageBuildInfo getPartsInfo(ImageBuildJob imageBuildJob) throws IOException, InterruptedException {
\r
385 final ImageBuildInfo imageBuildInfo = new ImageBuildInfo();
\r
386 final Semaphore compliteLock = new Semaphore(0);
\r
387 // ジョブリクエスト側に合成するイメージの情報を引き渡すように要求する.
\r
388 // loadPartsが非同期に行われる場合、すぐに制御を戻す.
\r
389 imageBuildJob.loadParts(new ImageSourceCollector() {
\r
390 // ジョブリクエスト側よりイメージサイズの設定として呼び出される
\r
391 public void setSize(Dimension size) {
\r
392 synchronized (imageBuildInfo) {
\r
393 imageBuildInfo.setRect(size.width, size.height);
\r
396 public void setImageBgColor(Color color) {
\r
397 synchronized (imageBuildInfo) {
\r
398 imageBuildInfo.setImageBgColor(color);
\r
401 public void setAffineTramsform(double[] param) {
\r
402 if (param != null && !(param.length == 4 || param.length == 6)) {
\r
403 throw new IllegalArgumentException("affineTransformParameter invalid length.");
\r
405 synchronized (imageBuildInfo) {
\r
406 imageBuildInfo.setAffineParamHolder(param);
\r
409 // ジョブリクエスト側よりパーツの登録として呼び出される
\r
410 public void setImageSource(Layer layer, ImageResource imageResource, ColorConvertParameter param) {
\r
411 synchronized (imageBuildInfo) {
\r
412 imageBuildInfo.add(new ImageBuildPartsInfo(
\r
413 imageBuildInfo.getPartsCount(), layer, imageResource, param));
\r
416 // ジョブリクエスト側よりイメージサイズとパーツの登録が完了したことを通知される.
\r
417 public void setComplite() {
\r
418 compliteLock.release();
\r
422 // ImageCollectorは非同期に呼び出されても良いことを想定している.
\r
423 // MAX_TIMEOUTを経過してもsetCompliteが呼び出されない場合、処理を放棄する.
\r
424 if (!compliteLock.tryAcquire(MAX_TIMEOUT, TimeUnit.SECONDS)) {
\r
425 throw new RuntimeException("ImageCollector Timeout.");
\r
427 return imageBuildInfo;
\r
431 * イメージビルド情報をもとにイメージを構築して返す.
\r
433 * @param imageBuildInfo
\r
435 * @throws IOException
\r
438 protected void buildImage(ImageBuildInfo imageBuildInfo) throws IOException {
\r
441 int w = imageBuildInfo.getRct().width;
\r
442 int h = imageBuildInfo.getRct().height;
\r
444 final BufferedImage canvas = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
\r
445 Graphics2D g = (Graphics2D) canvas.getGraphics();
\r
448 AppConfig appConfig = AppConfig.getInstance();
\r
449 if (appConfig.isEnableRenderingHints()) {
\r
450 g.setRenderingHint(
\r
451 RenderingHints.KEY_ALPHA_INTERPOLATION,
\r
452 RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
\r
453 g.setRenderingHint(
\r
454 RenderingHints.KEY_COLOR_RENDERING,
\r
455 RenderingHints.VALUE_COLOR_RENDER_QUALITY);
\r
456 g.setRenderingHint(
\r
457 RenderingHints.KEY_RENDERING,
\r
458 RenderingHints.VALUE_RENDER_QUALITY);
\r
461 // 各パーツを重ね合わせ順にカンバスに描画する
\r
462 imageLoader.unlockImages();
\r
463 for (ImageBuildPartsInfo partsInfo : imageBuildInfo.getPartsInfos()) {
\r
464 ImageResource imageFile = partsInfo.getFile();
\r
465 ColorConvertParameter colorConvParam = partsInfo.getColorParam();
\r
467 Layer layer = partsInfo.getLayer();
\r
468 String colorModelName = layer.getColorModelName();
\r
469 ColorModel colorModel = ColorModels.safeValueOf(colorModelName);
\r
471 LoadedImage loadedImage = imageLoader.load(imageFile,
\r
472 colorConvParam, colorModel);
\r
474 // イメージ構築に使用した各パーツの結果を格納する.
\r
475 imageBuildInfo.addUsedPartsInfo(partsInfo, loadedImage);
\r
478 BufferedImage img = loadedImage.getImage();
\r
479 g.drawImage(img, 0, 0, w, h, 0, 0, w, h, null);
\r
486 // アフィン処理を行う.(パラメータが指定されていれば)
\r
487 final BufferedImage affineTransformedCanvas;
\r
488 double[] affineTransformParameter = imageBuildInfo.getAffineParamHolder();
\r
489 if (affineTransformParameter == null || affineTransformParameter.length != 6) {
\r
490 affineTransformedCanvas = canvas;
\r
493 AffineTransform affineTransform = new AffineTransform(affineTransformParameter);
\r
494 AffineTransformOp affineTransformOp = new AffineTransformOp(affineTransform, AffineTransformOp.TYPE_BILINEAR);
\r
495 affineTransformedCanvas = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
\r
496 affineTransformOp.filter(canvas, affineTransformedCanvas);
\r
499 // 最終的にできあがったキャンバスを結果として格納する.
\r
500 imageBuildInfo.setCanvas(affineTransformedCanvas);
\r
504 * イメージ構築ジョブを要求します.<br>
\r
505 * 戻り値がtrueである場合は、ただちに完了したことを示します.<br>
\r
507 * @param imageBuildJob
\r
509 * @return 画像がただちに得られた場合はtrue、そうでなければfalse
\r
511 public boolean requestJob(final ImageBuildJob imageBuildJob) {
\r
512 if (imageBuildJob == null) {
\r
513 throw new IllegalArgumentException();
\r
517 final ImageBuildInfo imageBuildInfo;
\r
519 imageBuildInfo = getPartsInfo(imageBuildJob);
\r
521 } catch (Throwable ex) {
\r
523 imageBuildJob.handleException(ex);
\r
528 synchronized (imageBuildInfo) {
\r
530 final BufferedImage canvas;
\r
532 // 前回構築したパーツと同じであれば再構築せず、以前のものを使う
\r
533 if (imageBuildInfo.isAlreadyLoaded(lastUsedImageBuildInfo)) {
\r
534 canvas = lastUsedImageBuildInfo.getCanvas();
\r
538 buildImage(imageBuildInfo);
\r
539 canvas = imageBuildInfo.getCanvas();
\r
540 lastUsedImageBuildInfo = imageBuildInfo;
\r
543 // 完成したカンバスを合成結果として通知する.
\r
544 imageBuildJob.buildImage(new ImageOutput() {
\r
545 public BufferedImage getImageOutput() {
\r
548 public Color getImageBgColor() {
\r
549 return imageBuildInfo.getImageBgColor();
\r
554 } catch (Throwable ex) {
\r
556 imageBuildJob.handleException(ex);
\r
566 * 合成する個々のイメージ情報 .<br>
\r
567 * レイヤー順に順序づけられており、同一レイヤーであればOrder順に順序づけられます.<br>
\r
571 final class ImageBuildPartsInfo implements Comparable<ImageBuildPartsInfo> {
\r
575 private Layer layer;
\r
577 private ImageResource imageResource;
\r
579 private ColorConvertParameter colorParam;
\r
581 public ImageBuildPartsInfo(int order, Layer layer, ImageResource imageResource, ColorConvertParameter colorParam) {
\r
582 this.order = order;
\r
583 this.layer = layer;
\r
584 this.imageResource = imageResource;
\r
585 this.colorParam = colorParam;
\r
589 public int hashCode() {
\r
590 return order ^ layer.hashCode() ^ imageResource.hashCode();
\r
594 public boolean equals(Object obj) {
\r
598 if (obj != null && obj instanceof ImageBuildPartsInfo) {
\r
599 ImageBuildPartsInfo o = (ImageBuildPartsInfo) obj;
\r
600 return order == o.order && layer.equals(o.layer)
\r
601 && imageResource.equals(o.imageResource) && colorParam.equals(o.colorParam);
\r
606 public int compareTo(ImageBuildPartsInfo o) {
\r
608 int ret = layer.compareTo(o.layer);
\r
611 ret = order - o.order;
\r
614 // それでも同じであればイメージソースの固有の順序
\r
615 ret = imageResource.compareTo(o.imageResource);
\r
620 public int getOrder() {
\r
624 public Layer getLayer() {
\r
628 public ColorConvertParameter getColorParam() {
\r
632 public ImageResource getFile() {
\r
633 return imageResource;
\r