• R/O
  • HTTP
  • SSH
  • HTTPS

CharacterManaJ: Commit

キャラクターなんとかJの本体


Commit MetaInfo

Revisionf7743309c60637b1abe0830088b0e8692e45f2b0 (tree)
Time2019-01-16 02:34:24
Authorseraphy <seraphy@user...>
Commiterseraphy

Log Message

zipとpsd形式での保存に対応

ただし、psd形式はGimp、FireAlpaca、およびLebreOffice Drawなどでは正常に表示されるが、
本家であるAdobe系のアプリケーションではレイヤーがずれた状態で表示される。

Change Summary

Incremental Difference

--- a/src/main/java/charactermanaj/graphics/AsyncImageBuilder.java
+++ b/src/main/java/charactermanaj/graphics/AsyncImageBuilder.java
@@ -8,7 +8,7 @@ import java.util.logging.Logger;
88 * 各パーツ情報をもとに非同期にイメージを合成する
99 * @author seraphy
1010 */
11-public class AsyncImageBuilder extends ImageBuilder implements Runnable {
11+public class AsyncImageBuilder implements ImageBuilder, Runnable {
1212
1313 /**
1414 * ロガー
@@ -27,24 +27,24 @@ public class AsyncImageBuilder extends ImageBuilder implements Runnable {
2727 * @param ticket このイメージビルダでリクエストを受け付けた通し番号
2828 */
2929 void onQueueing(long ticket);
30-
30+
3131 /**
3232 * リクエストを処理するまえに破棄された場合に呼び出される.<br>
3333 */
3434 void onAbandoned();
3535 }
36-
36+
3737 /**
3838 * 同期オブジェクト
3939 */
4040 private final Object lock = new Object();
41-
41+
4242 /**
4343 * チケットのシリアルナンバー.<br>
4444 * リクエストがあるごとにインクリメントされる.<br>
4545 */
4646 private long ticketSerialNum = 0;
47-
47+
4848 /**
4949 * リクエストされているジョブ、なければnull
5050 */
@@ -54,22 +54,35 @@ public class AsyncImageBuilder extends ImageBuilder implements Runnable {
5454 * 停止フラグ(volatile)
5555 */
5656 private volatile boolean stopFlag;
57-
57+
5858 /**
5959 * スレッド
6060 */
6161 private Thread thread;
62-
62+
63+ /**
64+ * イメージビルダ
65+ */
66+ private ImageBuilder imageBuilder;
67+
6368 /**
6469 * イメージローダを指定して構築する.
6570 * @param imageLoader イメージローダー
6671 */
6772 public AsyncImageBuilder(ColorConvertedImageCachedLoader imageLoader) {
68- super(imageLoader);
73+ imageBuilder = new ImageBuilderImpl(imageLoader);
6974 thread = new Thread(this);
7075 thread.setDaemon(true);
7176 }
72-
77+
78+ /**
79+ * 同期イメージビルダを取得する。
80+ * @return
81+ */
82+ public ImageBuilder getImageBuilder() {
83+ return imageBuilder;
84+ }
85+
7386 /**
7487 * スレッドの実行部.
7588 */
@@ -95,12 +108,12 @@ public class AsyncImageBuilder extends ImageBuilder implements Runnable {
95108 lock.notifyAll();
96109 }
97110 // リクエストを処理する.
98- AsyncImageBuilder.super.requestJob(job);
99-
111+ imageBuilder.requestJob(job);
112+
100113 } catch (InterruptedException ex) {
101114 logger.log(Level.FINE, "AsyncImageBuilder thead interrupted.");
102115 // 割り込みされた場合、単にループを再開する.
103-
116+
104117 } catch (Exception ex) {
105118 logger.log(Level.SEVERE, "AsyncImageBuilder failed.", ex);
106119 // ジョブ合成中の予期せぬ例外はログに記録するのみで
@@ -110,7 +123,7 @@ public class AsyncImageBuilder extends ImageBuilder implements Runnable {
110123 }
111124 logger.log(Level.FINE, "AsyncImageBuilder thread stopped.");
112125 }
113-
126+
114127 /**
115128 * イメージ作成ジョブをリクエストする.<br>
116129 * イメージ作成ジョブは非同期に実行される.<br>
@@ -123,7 +136,7 @@ public class AsyncImageBuilder extends ImageBuilder implements Runnable {
123136 if (this.requestJob != null && this.requestJob instanceof AsyncImageBuildJob) {
124137 ((AsyncImageBuildJob) this.requestJob).onAbandoned();
125138 }
126-
139+
127140 // リクエストをセットして待機中のスレッドに通知を出す.
128141 this.requestJob = imageSource;
129142 if (imageSource != null && imageSource instanceof AsyncImageBuildJob) {
@@ -142,7 +155,7 @@ public class AsyncImageBuilder extends ImageBuilder implements Runnable {
142155 public boolean isAlive() {
143156 return thread.isAlive();
144157 }
145-
158+
146159 /**
147160 * スレッドを開始する.
148161 */
@@ -168,5 +181,4 @@ public class AsyncImageBuilder extends ImageBuilder implements Runnable {
168181 }
169182 }
170183 }
171-
172184 }
--- a/src/main/java/charactermanaj/graphics/ImageBuildJobAbstractAdaptor.java
+++ b/src/main/java/charactermanaj/graphics/ImageBuildJobAbstractAdaptor.java
@@ -52,7 +52,8 @@ public abstract class ImageBuildJobAbstractAdaptor implements AsyncImageBuilder.
5252 if (param == null) {
5353 param = new ColorConvertParameter();
5454 }
55- collector.setImageSource(layer, layerOrder, imageResource, param);
55+ String partsName = partsIdentifier.getPartsName();
56+ collector.setImageSource(partsName, layer, layerOrder, imageResource, param);
5657 }
5758 });
5859 collector.setComplite();
--- a/src/main/java/charactermanaj/graphics/ImageBuilder.java
+++ b/src/main/java/charactermanaj/graphics/ImageBuilder.java
@@ -29,12 +29,7 @@ import charactermanaj.model.Layer;
2929 *
3030 * @author seraphy
3131 */
32-public class ImageBuilder {
33-
34- /**
35- * 各パーツ情報の読み取りタイムアウト
36- */
37- private static final int MAX_TIMEOUT = 20; // Secs
32+public interface ImageBuilder {
3833
3934 /**
4035 * 各パーツ情報を設定するためのインターフェイス.<br>
@@ -75,6 +70,8 @@ public class ImageBuilder {
7570 * 複数パーツある場合は、これを繰り返し呼び出す.<br>
7671 * すべて呼び出したらsetCompliteを呼び出す.<br>
7772 *
73+ * @param partsName
74+ * パーツ名
7875 * @param layer
7976 * レイヤー
8077 * @param layerOrder
@@ -84,7 +81,7 @@ public class ImageBuilder {
8481 * @param param
8582 * 色変換情報
8683 */
87- void setImageSource(Layer layer, float layerOrder, ImageResource imageResource, ColorConvertParameter param);
84+ void setImageSource(String partsName, Layer layer, float layerOrder, ImageResource imageResource, ColorConvertParameter param);
8885
8986 /**
9087 * パーツの登録が完了したことを通知する。
@@ -148,6 +145,21 @@ public class ImageBuilder {
148145 void handleException(Throwable ex);
149146 }
150147
148+ public interface ImageBuildJob2 extends ImageBuildJob {
149+
150+ void onCreateLayerImage(String partsName, Layer layer, BufferedImage img);
151+ }
152+
153+ boolean requestJob(final ImageBuildJob imageBuildJob);
154+}
155+
156+class ImageBuilderImpl implements ImageBuilder {
157+
158+ /**
159+ * 各パーツ情報の読み取りタイムアウト
160+ */
161+ private static final int MAX_TIMEOUT = 20; // Secs
162+
151163 /**
152164 * イメージ構築に使用したパーツ情報
153165 *
@@ -157,19 +169,23 @@ public class ImageBuilder {
157169
158170 private final ImageBuildPartsInfo partsInfo;
159171
160- private final long lastModified;
172+ private final LoadedImage loadedImage;
161173
162174 public BuildedPartsInfo(ImageBuildPartsInfo partsInfo, LoadedImage loadedImage) {
163175 this.partsInfo = partsInfo;
164- this.lastModified = loadedImage.getLastModified();
176+ this.loadedImage = loadedImage;
165177 }
166178
167179 public ImageBuildPartsInfo getPartsInfo() {
168180 return partsInfo;
169181 }
170182
183+ public LoadedImage getLoadedImage() {
184+ return loadedImage;
185+ }
186+
171187 public long getLastModified() {
172- return lastModified;
188+ return loadedImage.getLastModified();
173189 }
174190 }
175191
@@ -279,6 +295,15 @@ public class ImageBuilder {
279295 }
280296
281297 /**
298+ * イメージに構築したパーツ情報を取得する。
299+ * 構築順序で返される。
300+ * @return パーツ情報と構築されたレイヤーイメージ
301+ */
302+ public List<BuildedPartsInfo> getBuildPartsInfos() {
303+ return buildPartsInfos;
304+ }
305+
306+ /**
282307 * イメージ構築結果を取得する.
283308 *
284309 * @return イメージ構築結果
@@ -364,7 +389,7 @@ public class ImageBuilder {
364389 * @param imageLoader
365390 * イメージローダー
366391 */
367- public ImageBuilder(ColorConvertedImageCachedLoader imageLoader) {
392+ public ImageBuilderImpl(ColorConvertedImageCachedLoader imageLoader) {
368393 if (imageLoader == null) {
369394 throw new IllegalArgumentException();
370395 }
@@ -391,16 +416,19 @@ public class ImageBuilder {
391416 // loadPartsが非同期に行われる場合、すぐに制御を戻す.
392417 imageBuildJob.loadParts(new ImageSourceCollector() {
393418 // ジョブリクエスト側よりイメージサイズの設定として呼び出される
419+ @Override
394420 public void setSize(Dimension size) {
395421 synchronized (imageBuildInfo) {
396422 imageBuildInfo.setRect(size.width, size.height);
397423 }
398424 }
425+ @Override
399426 public void setImageBgColor(Color color) {
400427 synchronized (imageBuildInfo) {
401428 imageBuildInfo.setImageBgColor(color);
402429 }
403430 }
431+ @Override
404432 public void setAffineTramsform(double[] param) {
405433 if (param != null && !(param.length == 4 || param.length == 6)) {
406434 throw new IllegalArgumentException("affineTransformParameter invalid length.");
@@ -410,14 +438,16 @@ public class ImageBuilder {
410438 }
411439 }
412440 // ジョブリクエスト側よりパーツの登録として呼び出される
413- public void setImageSource(Layer layer, float layerOrder, ImageResource imageResource,
441+ @Override
442+ public void setImageSource(String partsName, Layer layer, float layerOrder, ImageResource imageResource,
414443 ColorConvertParameter param) {
415444 synchronized (imageBuildInfo) {
416445 imageBuildInfo.add(new ImageBuildPartsInfo(
417- imageBuildInfo.getPartsCount(), layer, layerOrder, imageResource, param));
446+ partsName, imageBuildInfo.getPartsCount(), layer, layerOrder, imageResource, param));
418447 }
419448 }
420449 // ジョブリクエスト側よりイメージサイズとパーツの登録が完了したことを通知される.
450+ @Override
421451 public void setComplite() {
422452 compliteLock.release();
423453 }
@@ -512,6 +542,7 @@ public class ImageBuilder {
512542 * イメージを構築するジョブ
513543 * @return 画像がただちに得られた場合はtrue、そうでなければfalse
514544 */
545+ @Override
515546 public boolean requestJob(final ImageBuildJob imageBuildJob) {
516547 if (imageBuildJob == null) {
517548 throw new IllegalArgumentException();
@@ -544,6 +575,19 @@ public class ImageBuilder {
544575 lastUsedImageBuildInfo = imageBuildInfo;
545576 }
546577
578+ // 構築したレイヤーごとの画像を通知する
579+ if (imageBuildJob instanceof ImageBuildJob2) {
580+ ImageBuildJob2 callback = (ImageBuildJob2) imageBuildJob;
581+ for (BuildedPartsInfo bpInfo : lastUsedImageBuildInfo.getBuildPartsInfos()) {
582+ ImageBuildPartsInfo partsInfo = bpInfo.getPartsInfo();
583+ String partsName = partsInfo.getPartsName();
584+ Layer layer = partsInfo.getLayer();
585+ LoadedImage loadedImage = bpInfo.getLoadedImage();
586+ BufferedImage img = loadedImage.getImage();
587+ callback.onCreateLayerImage(partsName, layer, img);
588+ }
589+ }
590+
547591 // 完成したカンバスを合成結果として通知する.
548592 imageBuildJob.buildImage(new ImageOutput() {
549593 public BufferedImage getImageOutput() {
@@ -575,6 +619,11 @@ public class ImageBuilder {
575619 final class ImageBuildPartsInfo implements Comparable<ImageBuildPartsInfo> {
576620
577621 /**
622+ * パーツ名
623+ */
624+ private String partsName;
625+
626+ /**
578627 * 定義順
579628 */
580629 private int order;
@@ -590,8 +639,9 @@ final class ImageBuildPartsInfo implements Comparable<ImageBuildPartsInfo> {
590639
591640 private ColorConvertParameter colorParam;
592641
593- public ImageBuildPartsInfo(int order, Layer layer, float layerOrder, ImageResource imageResource,
642+ public ImageBuildPartsInfo(String partsName, int order, Layer layer, float layerOrder, ImageResource imageResource,
594643 ColorConvertParameter colorParam) {
644+ this.partsName = partsName;
595645 this.order = order;
596646 this.layer = layer;
597647 this.layerOrder = layerOrder;
@@ -601,7 +651,7 @@ final class ImageBuildPartsInfo implements Comparable<ImageBuildPartsInfo> {
601651
602652 @Override
603653 public int hashCode() {
604- return order ^ layer.hashCode() ^ imageResource.hashCode();
654+ return partsName.hashCode() ^ order ^ layer.hashCode() ^ imageResource.hashCode();
605655 }
606656
607657 @Override
@@ -611,7 +661,7 @@ final class ImageBuildPartsInfo implements Comparable<ImageBuildPartsInfo> {
611661 }
612662 if (obj != null && obj instanceof ImageBuildPartsInfo) {
613663 ImageBuildPartsInfo o = (ImageBuildPartsInfo) obj;
614- return order == o.order && layer.equals(o.layer)
664+ return order == o.order && partsName.equals(o.partsName) && layer.equals(o.layer)
615665 && imageResource.equals(o.imageResource) && colorParam.equals(o.colorParam);
616666 }
617667 return false;
@@ -635,6 +685,10 @@ final class ImageBuildPartsInfo implements Comparable<ImageBuildPartsInfo> {
635685 return ret;
636686 }
637687
688+ public String getPartsName() {
689+ return partsName;
690+ }
691+
638692 public int getOrder() {
639693 return order;
640694 }
--- a/src/main/java/charactermanaj/graphics/io/ImageSaveHelper.java
+++ b/src/main/java/charactermanaj/graphics/io/ImageSaveHelper.java
@@ -11,11 +11,18 @@ import java.awt.Insets;
1111 import java.awt.RenderingHints;
1212 import java.awt.image.BufferedImage;
1313 import java.io.File;
14+import java.io.FileOutputStream;
1415 import java.io.IOException;
1516 import java.io.OutputStream;
17+import java.nio.ByteBuffer;
18+import java.nio.channels.FileChannel;
19+import java.util.ArrayList;
1620 import java.util.Arrays;
21+import java.util.Collection;
22+import java.util.HashMap;
1723 import java.util.Iterator;
1824 import java.util.List;
25+import java.util.Map;
1926 import java.util.Properties;
2027 import java.util.logging.Level;
2128 import java.util.logging.Logger;
@@ -38,8 +45,12 @@ import javax.swing.JSpinner;
3845 import javax.swing.SpinnerNumberModel;
3946 import javax.swing.filechooser.FileFilter;
4047
48+import org.apache.tools.zip.ZipEntry;
49+import org.apache.tools.zip.ZipOutputStream;
50+
4151 import charactermanaj.graphics.io.OutputOption.PictureMode;
4252 import charactermanaj.graphics.io.OutputOption.ZoomRenderingType;
53+import charactermanaj.model.Layer;
4354 import charactermanaj.util.LocalizedMessageComboBoxRender;
4455 import charactermanaj.util.LocalizedResourcePropertyLoader;
4556
@@ -48,7 +59,7 @@ import charactermanaj.util.LocalizedResourcePropertyLoader;
4859 * イメージを保存するためのヘルパークラス.<br>
4960 */
5061 public class ImageSaveHelper {
51-
62+
5263 /**
5364 * ロガー
5465 */
@@ -74,7 +85,7 @@ public class ImageSaveHelper {
7485
7586 return isSupported(f);
7687 }
77-
88+
7889 protected boolean isSupported(File f) {
7990 // サポートしている拡張子のいずれかにマッチするか?
8091 // (大文字・小文字は区別しない.)
@@ -89,7 +100,7 @@ public class ImageSaveHelper {
89100
90101 /**
91102 * 現在の選択されたファイル名を取得し、そのファイル名がデフォルトの拡張子で終端していなければ
92- * デフォルトの拡張子を設定してファイルチューザに設定し直す.<Br>
103+ * デフォルトの拡張子を設定してファイルチューザに設定し直す.<Br>
93104 * @param fileChooser ファイルチューザ
94105 * @return デフォルトの拡張子で終端されたファイル
95106 */
@@ -98,7 +109,7 @@ public class ImageSaveHelper {
98109 if (outFile == null) {
99110 return null;
100111 }
101-
112+
102113 if ( !isSupported(outFile)) {
103114 String extName = "." + getSupprotedExtension()[0];
104115 outFile = new File(outFile.getPath() + extName);
@@ -106,7 +117,7 @@ public class ImageSaveHelper {
106117 }
107118 return outFile;
108119 }
109-
120+
110121 /**
111122 * サポートするファイルの拡張子を取得する.<br>
112123 * 最初のものがデフォルトの拡張子として用いられる.<br>
@@ -114,7 +125,7 @@ public class ImageSaveHelper {
114125 */
115126 protected abstract String[] getSupprotedExtension();
116127 }
117-
128+
118129 /**
119130 * PNGファイルフィルタ
120131 */
@@ -156,13 +167,41 @@ public class ImageSaveHelper {
156167 return new String[] {"bmp"};
157168 }
158169 };
159-
170+
171+ /**
172+ * ZIPファイルフィルタ
173+ */
174+ protected static final FileFilter zipFilter = new ImageSaveHelperFilter() {
175+ @Override
176+ public String getDescription() {
177+ return "Zip Archive(*.zip)";
178+ }
179+ @Override
180+ protected String[] getSupprotedExtension() {
181+ return new String[] {"zip"};
182+ }
183+ };
184+
185+ /**
186+ * PSDファイルフィルタ
187+ */
188+ protected static final FileFilter psdFilter = new ImageSaveHelperFilter() {
189+ @Override
190+ public String getDescription() {
191+ return "Adobe Photoshop(*.psd)";
192+ }
193+ @Override
194+ protected String[] getSupprotedExtension() {
195+ return new String[] {"psd"};
196+ }
197+ };
198+
160199 /**
161200 * このヘルパクラスで定義されているファイルフィルタのリスト
162201 */
163202 protected static final List<FileFilter> fileFilters = Arrays.asList(
164- pngFilter, jpegFilter, bmpFilter);
165-
203+ pngFilter, jpegFilter, bmpFilter, psdFilter, zipFilter);
204+
166205 /**
167206 * イメージビルダファクトリ
168207 */
@@ -173,17 +212,17 @@ public class ImageSaveHelper {
173212 * 未使用であれば規定値.<br>
174213 */
175214 protected OutputOption outputOption;
176-
215+
177216 /**
178217 * 最後に使用したディレクトリ
179218 */
180219 protected File lastUseSaveDir;
181-
220+
182221 /**
183222 * 最後に使用したフィルタ
184223 */
185224 protected FileFilter lastUseFilter = pngFilter;
186-
225+
187226 /**
188227 * 最後に使用したディレクトリを設定する
189228 * @param lastUseSaveDir 最後に使用したディレクトリ、設定しない場合はnull
@@ -191,7 +230,7 @@ public class ImageSaveHelper {
191230 public void setLastUseSaveDir(File lastUseSaveDir) {
192231 this.lastUseSaveDir = lastUseSaveDir;
193232 }
194-
233+
195234 /**
196235 * 最後に使用したディレクトリを取得する
197236 * @return 最後に使用したディレクトリ、なければnull
@@ -199,7 +238,7 @@ public class ImageSaveHelper {
199238 public File getLastUsedSaveDir() {
200239 return lastUseSaveDir;
201240 }
202-
241+
203242
204243 /**
205244 * コンストラクタ
@@ -235,7 +274,7 @@ public class ImageSaveHelper {
235274 if (selfilter instanceof ImageSaveHelperFilter) {
236275 outFile = ((ImageSaveHelperFilter) selfilter).supplyDefaultExtension(this);
237276 }
238-
277+
239278 // ファイルが存在すれば上書き確認する.
240279 if (outFile.exists()) {
241280 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
@@ -249,15 +288,15 @@ public class ImageSaveHelper {
249288 return;
250289 }
251290 }
252-
291+
253292 super.approveSelection();
254293 }
255294 };
256-
295+
257296 // // アクセサリパネルの追加˙
258297 // final OutputOptionPanel accessoryPanel = new OutputOptionPanel(this.outputOption);
259298 // fileChooser.setAccessory(accessoryPanel);
260-
299+
261300 // ファイルフィルタ設定
262301 fileChooser.setAcceptAllFileFilterUsed(false);
263302 for (FileFilter fileFilter : fileFilters) {
@@ -277,7 +316,7 @@ public class ImageSaveHelper {
277316 if (ret != JFileChooser.APPROVE_OPTION) {
278317 return null;
279318 }
280-
319+
281320 // // 出力オプションの保存
282321 // OutputOption outputOption = accessoryPanel.getOutputOption();
283322 // this.outputOption = outputOption;
@@ -286,15 +325,15 @@ public class ImageSaveHelper {
286325 File outFile = fileChooser.getSelectedFile();
287326 lastUseSaveDir = outFile.getParentFile();
288327 lastUseFilter = fileChooser.getFileFilter();
289-
328+
290329 // 選択したファイルを返す.
291- return outFile;
330+ return outFile;
292331 }
293-
332+
294333 public OutputOption getOutputOption() {
295334 return outputOption.clone();
296335 }
297-
336+
298337 public void setOutputOption(OutputOption outputOption) {
299338 if (outputOption == null) {
300339 throw new IllegalArgumentException();
@@ -303,6 +342,22 @@ public class ImageSaveHelper {
303342 }
304343
305344 /**
345+ * ファイルから拡張子を取得する。
346+ * @param outFile ファイル名
347+ * @return 拡張子
348+ * @throws IOException 拡張子がない場合
349+ */
350+ public String getFileExtension(File outFile) throws IOException {
351+ // ファイル名から拡張子を取り出します.
352+ String fname = outFile.getName();
353+ int extpos = fname.lastIndexOf(".");
354+ if (extpos < 0) {
355+ throw new IOException("missing file extension.");
356+ }
357+ return fname.substring(extpos + 1).toLowerCase();
358+ }
359+
360+ /**
306361 * ファイル名を指定してイメージをファイルに出力します.<br>
307362 * 出力形式は拡張子より判定します.<br>
308363 * サポートされていない拡張子の場合はIOException例外が発生します.<br>
@@ -317,27 +372,21 @@ public class ImageSaveHelper {
317372 if (img == null || outFile == null) {
318373 throw new IllegalArgumentException();
319374 }
320-
321- // ファイル名から拡張子を取り出します.
322- String fname = outFile.getName();
323- int extpos = fname.lastIndexOf(".");
324- if (extpos < 0) {
325- throw new IOException("missing file extension.");
326- }
327- String ext = fname.substring(extpos + 1).toLowerCase();
375+
376+ String ext = getFileExtension(outFile);
328377
329378 // 拡張子に対するImageIOのライタを取得します.
330379 Iterator<ImageWriter> ite = ImageIO.getImageWritersBySuffix(ext);
331380 if (!ite.hasNext()) {
332381 throw new IOException("unsupported file extension: " + ext);
333382 }
334-
383+
335384 ImageWriter iw = ite.next();
336-
385+
337386 // ライタを使いイメージを書き込みます.
338387 savePicture(img, imgBgColor, iw, outFile, warnings);
339388 }
340-
389+
341390 /**
342391 * イメージをMIMEで指定された形式で出力します.
343392 * @param img イメージ
@@ -370,7 +419,7 @@ public class ImageSaveHelper {
370419 savePicture(img, imgBgColor, iw, outstm, warnings);
371420 outstm.flush();
372421 }
373-
422+
374423 protected void savePicture(BufferedImage img, Color imgBgColor, ImageWriter iw,
375424 Object output, final StringBuilder warnings)
376425 throws IOException {
@@ -440,7 +489,7 @@ public class ImageSaveHelper {
440489 iw.dispose();
441490 }
442491 }
443-
492+
444493 /**
445494 * ARGB形式から、アルファチャンネルを削除し、かわりに背景色を設定したBGR形式画像を返します.<br>
446495 * JPEG画像として用いることを想定しています.<br>
@@ -514,6 +563,121 @@ public class ImageSaveHelper {
514563 }
515564 return tmpImg;
516565 }
566+
567+ public static class LayerImage {
568+
569+ private String partsName;
570+
571+ private Layer layer;
572+
573+ private BufferedImage image;
574+
575+ public LayerImage(String partsName, Layer layer, BufferedImage image) {
576+ super();
577+ this.partsName = partsName;
578+ this.layer = layer;
579+ this.image = image;
580+ }
581+
582+ public String getPartsName() {
583+ return partsName;
584+ }
585+
586+ public Layer getLayer() {
587+ return layer;
588+ }
589+
590+ public BufferedImage getImage() {
591+ return image;
592+ }
593+
594+ @Override
595+ public String toString() {
596+ return "(partsName=" + partsName + ", layer=" + layer + ")";
597+ }
598+ }
599+
600+ /**
601+ * zipにレイヤーごとの画像とプレビュー画像をまとめて保存する
602+ * @param outFile
603+ * @param layerImages
604+ * @param compositeImg
605+ * @throws IOException
606+ */
607+ public void saveToZip(File outFile, Collection<LayerImage> layerImages, BufferedImage compositeImg)
608+ throws IOException {
609+ ZipOutputStream zos = new ZipOutputStream(outFile);
610+ try {
611+ if (layerImages != null) {
612+ for (LayerImage layerImage : layerImages) {
613+ String partsName = layerImage.getPartsName();
614+ Layer layer = layerImage.getLayer();
615+ String dir = layer.getDir();
616+ String fname = dir + "/" + partsName + ".png";
617+
618+ ZipEntry zipEntry = new ZipEntry(fname);
619+ zos.putNextEntry(zipEntry);
620+ ImageIO.write(layerImage.getImage(), "png", zos);
621+ zos.closeEntry();
622+ }
623+ }
624+ if (compositeImg != null) {
625+ ZipEntry zipEntry = new ZipEntry("preview.png");
626+ zos.putNextEntry(zipEntry);
627+ ImageIO.write(compositeImg, "png", zos);
628+ zos.closeEntry();
629+ }
630+
631+ } finally {
632+ zos.close();
633+ }
634+ }
635+
636+ /**
637+ * PSD形式で保存する
638+ * @param outFile
639+ * @param layerImages
640+ * @param compositeImg
641+ * @throws IOException
642+ */
643+ public void saveToPSD(File outFile, Collection<LayerImage> layerImages, BufferedImage compositeImg)
644+ throws IOException {
645+ List<PSDCreator.LayerData> layerDatas = new ArrayList<PSDCreator.LayerData>();
646+ Map<String, Integer> dupchk = new HashMap<String, Integer>();
647+ for (LayerImage layerImage : layerImages) {
648+ Layer layer = layerImage.getLayer();
649+ String layerName = layer.getDir();
650+ Integer cnt = dupchk.get(layerName);
651+ if (cnt == null) {
652+ cnt = 1;
653+ } else {
654+ cnt = cnt + 1;
655+ layerName = layerName + "(" + cnt + ")";
656+ }
657+ dupchk.put(layerName, cnt);
658+
659+ BufferedImage img = layerImage.getImage();
660+ PSDCreator.LayerData layerData = new PSDCreator.LayerData(layerName, img);
661+ layerDatas.add(layerData);
662+ }
663+ byte[] psdContents = PSDCreator.createPSD(layerDatas);
664+
665+ FileOutputStream fos = new FileOutputStream(outFile);
666+ try {
667+ FileChannel fc = fos.getChannel();
668+ try {
669+ ByteBuffer buf = ByteBuffer.wrap(psdContents);
670+ while (buf.hasRemaining()) {
671+ fc.write(buf);
672+ }
673+
674+ } finally {
675+ fc.close();
676+ }
677+ } finally {
678+ fos.close();
679+ }
680+ }
517681 }
518682
519683 /**
@@ -524,18 +688,18 @@ class OutputOptionPanel extends JPanel {
524688 private static final long serialVersionUID = 1L;
525689
526690 private JSpinner jpegQualitySpinner;
527-
691+
528692 private JCheckBox lblZoom;
529-
693+
530694 private JSpinner zoomSpinner;
531-
695+
532696 private JComboBox zoomAlgoCombo;
533-
697+
534698 private JComboBox pictureModeCombo;
535-
699+
536700 private JCheckBox checkForceBgColor;
537701
538-
702+
539703 public OutputOptionPanel() {
540704 this(new OutputOption());
541705 }
@@ -566,7 +730,7 @@ class OutputOptionPanel extends JPanel {
566730 gbc.gridwidth = 1;
567731 gbc.weightx = 0.;
568732 add(Box.createHorizontalStrut(6), gbc);
569-
733+
570734 // JPEG
571735 gbc.gridx = 0;
572736 gbc.gridy = 0;
@@ -576,7 +740,7 @@ class OutputOptionPanel extends JPanel {
576740 strings.getProperty("outputOption.jpeg.caption"));
577741 lblJpeg.setFont(lblJpeg.getFont().deriveFont(Font.BOLD));
578742 add(lblJpeg, gbc);
579-
743+
580744 gbc.gridx = 1;
581745 gbc.gridy = 1;
582746 gbc.gridwidth = 1;
@@ -584,10 +748,10 @@ class OutputOptionPanel extends JPanel {
584748 add(new JLabel(
585749 strings.getProperty("outputOption.jpeg.quality"),
586750 JLabel.RIGHT), gbc);
587-
751+
588752 SpinnerNumberModel spmodel = new SpinnerNumberModel(100, 10, 100, 1);
589753 this.jpegQualitySpinner = new JSpinner(spmodel);
590-
754+
591755 gbc.gridx = 2;
592756 gbc.gridy = 1;
593757 gbc.gridwidth = 1;
@@ -607,7 +771,7 @@ class OutputOptionPanel extends JPanel {
607771 lblZoom = new JCheckBox(strings.getProperty("outputOption.zoom.caption"));
608772 lblZoom.setFont(lblJpeg.getFont().deriveFont(Font.BOLD));
609773 add(lblZoom, gbc);
610-
774+
611775 gbc.gridx = 1;
612776 gbc.gridy = 3;
613777 gbc.gridwidth = 1;
@@ -615,15 +779,15 @@ class OutputOptionPanel extends JPanel {
615779 gbc.insets = new Insets(3, 3, 3, 3);
616780 add(new JLabel(
617781 strings.getProperty("outputOption.zoom.factor"), JLabel.RIGHT), gbc);
618-
782+
619783 SpinnerNumberModel zoomSpModel = new SpinnerNumberModel(100, 20, 800, 1);
620784 this.zoomSpinner = new JSpinner(zoomSpModel);
621-
785+
622786 gbc.gridx = 2;
623787 gbc.gridy = 3;
624788 gbc.gridwidth = 1;
625789 add(zoomSpinner, gbc);
626-
790+
627791 gbc.gridx = 3;
628792 gbc.gridy = 3;
629793 gbc.gridwidth = 1;
@@ -636,7 +800,7 @@ class OutputOptionPanel extends JPanel {
636800 add(new JLabel(
637801 strings.getProperty("outputOption.zoom.renderingMode"),
638802 JLabel.RIGHT), gbc);
639-
803+
640804 this.zoomAlgoCombo = new JComboBox(ZoomRenderingType.values());
641805 this.zoomAlgoCombo.setRenderer(new LocalizedMessageComboBoxRender(strings));
642806 gbc.gridx = 2;
@@ -644,7 +808,7 @@ class OutputOptionPanel extends JPanel {
644808 gbc.gridwidth = 2;
645809 gbc.weightx = 0.;
646810 add(zoomAlgoCombo, gbc);
647-
811+
648812 // 画像モード
649813 gbc.gridx = 0;
650814 gbc.gridy = 5;
@@ -654,7 +818,7 @@ class OutputOptionPanel extends JPanel {
654818 strings.getProperty("outputOption.picture"));
655819 lblPictureMode.setFont(lblJpeg.getFont().deriveFont(Font.BOLD));
656820 add(lblPictureMode, gbc);
657-
821+
658822 gbc.gridx = 1;
659823 gbc.gridy = 6;
660824 gbc.gridwidth = 1;
@@ -662,7 +826,7 @@ class OutputOptionPanel extends JPanel {
662826 add(new JLabel(
663827 strings.getProperty("outputOption.picture.type"),
664828 JLabel.RIGHT), gbc);
665-
829+
666830 this.pictureModeCombo = new JComboBox(PictureMode.values());
667831 this.pictureModeCombo.setRenderer(
668832 new LocalizedMessageComboBoxRender(strings));
@@ -670,24 +834,24 @@ class OutputOptionPanel extends JPanel {
670834 gbc.gridy = 6;
671835 gbc.gridwidth = 2;
672836 gbc.weightx = 1.;
673- add(pictureModeCombo, gbc);
674-
837+ add(pictureModeCombo, gbc);
838+
675839 gbc.gridx = 1;
676840 gbc.gridy = 7;
677841 gbc.gridwidth = 3;
678842 gbc.weightx = 0.;
679843 checkForceBgColor = new JCheckBox(
680- strings.getProperty("outputOption.picture.forceBgColor"));
844+ strings.getProperty("outputOption.picture.forceBgColor"));
681845 add(checkForceBgColor, gbc);
682-
846+
683847 JPanel pnlBgAlpha = new JPanel(new BorderLayout(3, 3));
684848 pnlBgAlpha.add(new JLabel("背景アルファ"), BorderLayout.WEST);
685-
849+
686850 SpinnerNumberModel bgAlphaModel = new SpinnerNumberModel(255, 0, 255, 1);
687851 JSpinner bgAlphaSpinner = new JSpinner(bgAlphaModel);
688852
689853 pnlBgAlpha.add(bgAlphaSpinner, BorderLayout.CENTER);
690-
854+
691855 gbc.gridx = 1;
692856 gbc.gridy = 8;
693857 gbc.gridwidth = 3;
@@ -701,16 +865,16 @@ class OutputOptionPanel extends JPanel {
701865 gbc.weightx = 1.;
702866 gbc.weighty = 1.;
703867 add(Box.createGlue(), gbc);
704-
868+
705869 // update
706870 setOutputOption(outputOption);
707871 }
708-
872+
709873 public void setOutputOption(OutputOption outputOption) {
710874 if (outputOption == null) {
711875 outputOption = new OutputOption();
712876 }
713-
877+
714878 jpegQualitySpinner.setValue((int) (outputOption.getJpegQuality() * 100));
715879 lblZoom.setSelected(outputOption.isEnableZoom());
716880 zoomSpinner.setValue((int) (outputOption.getZoomFactor() * 100));
@@ -728,7 +892,7 @@ class OutputOptionPanel extends JPanel {
728892 outputOption.setZoomRenderingType((ZoomRenderingType) zoomAlgoCombo.getSelectedItem());
729893 outputOption.setPictureMode((PictureMode) pictureModeCombo.getSelectedItem());
730894 outputOption.setForceBgColor(checkForceBgColor.isSelected());
731-
895+
732896 return outputOption;
733897 }
734898 }
--- /dev/null
+++ b/src/main/java/charactermanaj/graphics/io/PSDCreator.java
@@ -0,0 +1,308 @@
1+package charactermanaj.graphics.io;
2+
3+import java.awt.Graphics2D;
4+import java.awt.image.BufferedImage;
5+import java.awt.image.DataBufferInt;
6+import java.awt.image.WritableRaster;
7+import java.io.ByteArrayOutputStream;
8+import java.io.DataOutputStream;
9+import java.io.IOException;
10+import java.util.Collection;
11+
12+/**
13+ * 複数レイヤー画像をPSD形式のデータとして作成する。
14+ * https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/
15+ *
16+ * @author seraphy
17+ */
18+public final class PSDCreator {
19+
20+ /**
21+ * レイヤーデータ
22+ */
23+ public static class LayerData {
24+
25+ /**
26+ * レイヤー名
27+ */
28+ private String layerName;
29+
30+ /**
31+ * レイヤーの画像(TYPE_INT_ARGB限定)
32+ */
33+ private BufferedImage image;
34+
35+ public LayerData(String layerName, BufferedImage image) {
36+ this.layerName = layerName;
37+ this.image = image;
38+ }
39+
40+ public String getLayerName() {
41+ return layerName;
42+ }
43+
44+ public BufferedImage getImage() {
45+ return image;
46+ }
47+ }
48+
49+ /**
50+ * プライベートコンストラクタ
51+ */
52+ private PSDCreator() {
53+ super();
54+ }
55+
56+ /**
57+ * レイヤーを指定してPSDデータを作成する
58+ * @param layerDatas レイヤーのコレクション、順番に重ねられる
59+ * @return PSDデータ
60+ * @throws IOException
61+ */
62+ public static byte[] createPSD(Collection<LayerData> layerDatas) throws IOException {
63+ if (layerDatas == null) {
64+ throw new NullPointerException("layerDatas is required.");
65+ }
66+ if (layerDatas.isEmpty()) {
67+ throw new IllegalArgumentException("layerDatas must not be empty.");
68+ }
69+
70+ BufferedImage cimg = createCompositeImage(layerDatas);
71+ int width = cimg.getWidth();
72+ int height = cimg.getHeight();
73+
74+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
75+ DataOutputStream dos = new DataOutputStream(bos);
76+
77+ dos.write("8BPS".getBytes());
78+ dos.writeShort(1);
79+ dos.write(new byte[6]); // reserved 6bytes
80+
81+ dos.writeShort(4); // argb
82+
83+ dos.writeInt(height);
84+ dos.writeInt(width);
85+
86+ int depth = 8;
87+ dos.writeShort(depth);
88+
89+ dos.writeShort(3); // ColorMode=RGB(3)
90+
91+ dos.writeInt(0); // カラーモードセクションなし
92+ dos.writeInt(0); // リソースセクションなし
93+
94+ // レイヤーマスクセクション
95+ byte[] layerMaskSection = createLayerMaskSection(layerDatas);
96+ dos.writeInt(layerMaskSection.length);
97+ dos.write(layerMaskSection);
98+
99+ // 画像セクション
100+ dos.writeShort(0); // RAW
101+ byte[][] channelDatas = createChannels(cimg);
102+ int[] channelMap = { 1, 2, 3, 0 }; // R, G, B, Aにマップ
103+ for (int channel = 0; channel < channelMap.length; channel++) {
104+ byte[] channelData = channelDatas[channelMap[channel]];
105+ dos.write(channelData);
106+ }
107+
108+ return bos.toByteArray();
109+ }
110+
111+ /**
112+ * レイヤーマスクセクションを作成する
113+ * @param layerDatas
114+ * @return
115+ * @throws IOException
116+ */
117+ private static byte[] createLayerMaskSection(Collection<LayerData> layerDatas) throws IOException {
118+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
119+ DataOutputStream dos = new DataOutputStream(bos);
120+
121+ byte[] layerData = createLayerData(layerDatas);
122+ dos.writeInt(layerData.length);
123+ dos.write(layerData);
124+
125+ return bos.toByteArray();
126+ }
127+
128+ /**
129+ * レイヤーデータの作成
130+ * @param layerDatas
131+ * @return
132+ * @throws IOException
133+ */
134+ private static byte[] createLayerData(Collection<LayerData> layerDatas) throws IOException {
135+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
136+ DataOutputStream dos = new DataOutputStream(bos);
137+
138+ int numOfLayers = layerDatas.size();
139+ dos.writeShort(numOfLayers); // non pre-multiplied
140+
141+ short[] channels = { -1, 0, 1, 2 }; // ALPHA, RED, GREEN, BLUE
142+
143+ for (LayerData layerData : layerDatas) {
144+ String layerName = layerData.getLayerName();
145+ BufferedImage image = layerData.getImage();
146+ int width = image.getWidth();
147+ int height = image.getHeight();
148+
149+ dos.writeInt(0); // top
150+ dos.writeInt(0); // left
151+ dos.writeInt(height); // bottom
152+ dos.writeInt(width); // right
153+
154+ dos.writeShort(channels.length);
155+
156+ int rawSize = width * height;
157+ for (int channel = 0; channel < channels.length; channel++) {
158+ dos.writeShort(channels[channel]);
159+ dos.writeInt(rawSize);
160+ }
161+
162+ dos.write("8BIM".getBytes());
163+ dos.write("norm".getBytes());
164+
165+ dos.write((byte) 255); // opacity
166+ dos.write((byte) 0); // clipping
167+ dos.write((byte) 0); // protection
168+ dos.write((byte) 0); // filler
169+
170+ byte[] layerMaskData = createLayerMaskData();
171+ byte[] layerBlendingData = createLayerBlendingData();
172+ byte[] layerNameData = createLayerName(layerName);
173+ int lenOfAdditional = layerMaskData.length + layerBlendingData.length + layerNameData.length;
174+
175+ dos.writeInt(lenOfAdditional);
176+ dos.write(layerMaskData);
177+ dos.write(layerBlendingData);
178+ dos.write(layerNameData);
179+ }
180+
181+ for (LayerData layerData : layerDatas) {
182+ BufferedImage image = layerData.getImage();
183+
184+ byte[][] channelsData = createChannels(image);
185+
186+ for (int channel = 0; channel < channels.length; channel++) {
187+ dos.writeShort(0); // RAW
188+
189+ byte[] channelData = channelsData[channel];
190+ dos.write(channelData);
191+ }
192+ }
193+
194+ return bos.toByteArray();
195+ }
196+
197+ /**
198+ * 空のレイヤーマスクデータ作成
199+ * @return
200+ * @throws IOException
201+ */
202+ private static byte[] createLayerMaskData() throws IOException {
203+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
204+ DataOutputStream dos = new DataOutputStream(bos);
205+ dos.writeInt(0);
206+ return bos.toByteArray();
207+ }
208+
209+ /**
210+ * 空のレイヤーブレンダーデータの作成
211+ * @return
212+ * @throws IOException
213+ */
214+ private static byte[] createLayerBlendingData() throws IOException {
215+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
216+ DataOutputStream dos = new DataOutputStream(bos);
217+ dos.writeInt(0);
218+ return bos.toByteArray();
219+ }
220+
221+ /**
222+ * レイヤー名の作成
223+ * @param layerName
224+ * @return
225+ * @throws IOException
226+ */
227+ private static byte[] createLayerName(String layerName) throws IOException {
228+ byte[] nameBuf = layerName.getBytes("UTF-8");
229+ int layerNameSize = 1 + nameBuf.length; // PASCAL文字列長 (16の倍数サイズ)
230+ int blockSize = (layerNameSize / 4) * 4 + ((layerNameSize % 4 > 0) ? 4 : 0);
231+ int paddingSize = blockSize - layerNameSize;
232+
233+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
234+ DataOutputStream dos = new DataOutputStream(bos);
235+ dos.write((byte) nameBuf.length);
236+ dos.write(nameBuf);
237+ dos.write(new byte[paddingSize]);
238+ return bos.toByteArray();
239+ }
240+
241+ /**
242+ * 32ビットARGB形式のBuffeedImageを受け取り、
243+ * ARGBのbyte[][]配列に変換して返す。
244+ * @param img イメージ
245+ * @return チャネル別配列
246+ */
247+ private static byte[][] createChannels(BufferedImage img) {
248+ WritableRaster raster = img.getRaster();
249+ DataBufferInt buffer = (DataBufferInt) raster.getDataBuffer();
250+ int[] pixels = buffer.getData();
251+
252+ int width = img.getWidth();
253+ int height = img.getHeight();
254+ int mx = width * height;
255+ byte[][] channels = new byte[4][mx];
256+ for (int idx = 0; idx < mx; idx++) {
257+ int argb = pixels[idx];
258+
259+ int alpha = (argb >> 24) & 0xff;
260+ int red = (argb >> 16) & 0xff;
261+ int green = (argb >> 8) & 0xff;
262+ int blue = argb & 0xff;
263+
264+ channels[0][idx] = (byte) alpha;
265+ channels[1][idx] = (byte) red;
266+ channels[2][idx] = (byte) green;
267+ channels[3][idx] = (byte) blue;
268+ }
269+
270+ return channels;
271+ }
272+
273+ /**
274+ * レイヤーコレクションを重ねて1つの画像にして返す
275+ * @param layerDatas レイヤーコレクション
276+ * @return 重ね合わせた画像
277+ */
278+ private static BufferedImage createCompositeImage(Collection<LayerData> layerDatas) {
279+ int width = 0;
280+ int height = 0;
281+ for (LayerData layerData : layerDatas) {
282+ BufferedImage img = layerData.getImage();
283+ int w = img.getWidth();
284+ int h = img.getHeight();
285+ if (w > width) {
286+ width = w;
287+ }
288+ if (h > height) {
289+ height = h;
290+ }
291+ }
292+
293+ BufferedImage cimg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
294+
295+ Graphics2D g = cimg.createGraphics();
296+ try {
297+ for (LayerData layerData : layerDatas) {
298+ BufferedImage img = layerData.getImage();
299+ int w = img.getWidth();
300+ int h = img.getHeight();
301+ g.drawImage(img, 0, 0, w, h, 0, 0, w, h, null);
302+ }
303+ } finally {
304+ g.dispose();
305+ }
306+ return cimg;
307+ }
308+}
--- a/src/main/java/charactermanaj/ui/MainFrame.java
+++ b/src/main/java/charactermanaj/ui/MainFrame.java
@@ -69,8 +69,13 @@ import charactermanaj.clipboardSupport.ClipboardUtil;
6969 import charactermanaj.graphics.AsyncImageBuilder;
7070 import charactermanaj.graphics.ColorConvertedImageCachedLoader;
7171 import charactermanaj.graphics.ImageBuildJobAbstractAdaptor;
72+import charactermanaj.graphics.ImageBuilder;
7273 import charactermanaj.graphics.ImageBuilder.ImageOutput;
74+import charactermanaj.graphics.ImageBuilder.ImageSourceCollector;
75+import charactermanaj.graphics.filters.ColorConvertParameter;
76+import charactermanaj.graphics.io.ImageResource;
7377 import charactermanaj.graphics.io.ImageSaveHelper;
78+import charactermanaj.graphics.io.ImageSaveHelper.LayerImage;
7479 import charactermanaj.graphics.io.OutputOption;
7580 import charactermanaj.graphics.io.UkagakaImageSaveHelper;
7681 import charactermanaj.model.AppConfig;
@@ -81,6 +86,7 @@ import charactermanaj.model.CharacterDataChangeObserver;
8186 import charactermanaj.model.ColorGroup;
8287 import charactermanaj.model.CustomLayerOrder;
8388 import charactermanaj.model.CustomLayerOrderKey;
89+import charactermanaj.model.Layer;
8490 import charactermanaj.model.LayerOrderMapper;
8591 import charactermanaj.model.PartsCategory;
8692 import charactermanaj.model.PartsColorInfo;
@@ -92,6 +98,7 @@ import charactermanaj.model.WorkingSet;
9298 import charactermanaj.model.io.CharacterDataPersistent;
9399 import charactermanaj.model.io.CustomLayerOrderPersist;
94100 import charactermanaj.model.io.CustomLayerOrderPersist.CustomLayerOrderPersistListener;
101+import charactermanaj.model.io.PartsImageCollectionParser;
95102 import charactermanaj.model.io.PartsImageDirectoryWatchAgent;
96103 import charactermanaj.model.io.PartsImageDirectoryWatchAgentFactory;
97104 import charactermanaj.model.io.PartsImageDirectoryWatchEvent;
@@ -1708,7 +1715,7 @@ public class MainFrame extends JFrame
17081715 imageSaveHelper.setOutputOption(outputOption);
17091716
17101717 // ファイルダイアログ表示
1711- File outFile = imageSaveHelper.showSaveFileDialog(this);
1718+ final File outFile = imageSaveHelper.showSaveFileDialog(this);
17121719 if (outFile == null) {
17131720 return;
17141721 }
@@ -1720,7 +1727,26 @@ public class MainFrame extends JFrame
17201727
17211728 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
17221729 try {
1723- imageSaveHelper.savePicture(img, imgBgColor, outFile, warnings);
1730+ String ext = imageSaveHelper.getFileExtension(outFile);
1731+ if ("zip".equals(ext)) {
1732+ saveLayers(outFile, new SaveLayerCallback() {
1733+ @Override
1734+ public void collect(List<LayerImage> layerImages, BufferedImage compositeImg)
1735+ throws IOException {
1736+ imageSaveHelper.saveToZip(outFile, layerImages, compositeImg);
1737+ }
1738+ });
1739+ } else if ("psd".equals(ext)) {
1740+ saveLayers(outFile, new SaveLayerCallback() {
1741+ @Override
1742+ public void collect(List<LayerImage> layerImages, BufferedImage compositeImg)
1743+ throws IOException {
1744+ imageSaveHelper.saveToPSD(outFile, layerImages, compositeImg);
1745+ }
1746+ });
1747+ } else {
1748+ imageSaveHelper.savePicture(img, imgBgColor, outFile, warnings);
1749+ }
17241750
17251751 } finally {
17261752 setCursor(Cursor.getDefaultCursor());
@@ -1734,6 +1760,59 @@ public class MainFrame extends JFrame
17341760 }
17351761 }
17361762
1763+ private interface SaveLayerCallback {
1764+ void collect(List<ImageSaveHelper.LayerImage> layerImages, BufferedImage compositeImg) throws IOException;
1765+ }
1766+
1767+ private void saveLayers(final File outFile, SaveLayerCallback callback) throws IOException {
1768+ final PartsImageCollectionParser partsImageCollectorParser = new PartsImageCollectionParser(characterData);
1769+ final List<ImageSaveHelper.LayerImage> layerImages = new ArrayList<ImageSaveHelper.LayerImage>();
1770+ final BufferedImage[] result = new BufferedImage[1];
1771+ ImageBuilder syncImgBuilder = this.imageBuilder.getImageBuilder(); // 同期型のイメージビルダを取得する
1772+ syncImgBuilder.requestJob(new ImageBuilder.ImageBuildJob2() {
1773+ @Override
1774+ public void loadParts(final ImageSourceCollector collector) throws IOException {
1775+ PartsSet partsSet = partsSelectionManager.createPartsSet();
1776+ partsSet.getActiveCustomLayerPatternIds();
1777+ LayerOrderMapper layerOrderMapper = customLayerPatternMgr.getLayerOrderMapper();
1778+ collector.setSize(partsImageCollectorParser.getPartsSpecResolver().getImageSize());
1779+ collector.setImageBgColor(partsSet.getBgColor());
1780+ collector.setAffineTramsform(partsSet.getAffineTransformParameter());
1781+ partsImageCollectorParser.parse(partsSet, layerOrderMapper,
1782+ new PartsImageCollectionParser.PartsImageCollectionHandler() {
1783+ public void detectImageSource(PartsIdentifier partsIdentifier,
1784+ Layer layer, float layerOrder, ImageResource imageResource,
1785+ ColorConvertParameter param) {
1786+ if (param == null) {
1787+ param = new ColorConvertParameter();
1788+ }
1789+ String partsName = partsIdentifier.getPartsName();
1790+ collector.setImageSource(partsName, layer, layerOrder, imageResource, param);
1791+ }
1792+ });
1793+ collector.setComplite();
1794+ }
1795+
1796+ @Override
1797+ public void handleException(Throwable ex) {
1798+ ErrorMessageHelper.showErrorDialog(MainFrame.this, ex);
1799+ }
1800+
1801+ @Override
1802+ public void buildImage(ImageOutput output) {
1803+ result[0] = output.getImageOutput();
1804+ }
1805+
1806+ @Override
1807+ public void onCreateLayerImage(String partsName, Layer layer, BufferedImage img) {
1808+ layerImages.add(new ImageSaveHelper.LayerImage(partsName,layer, img));
1809+ }
1810+ });
1811+
1812+ // 集まった各レイヤーの画像と、合成された画像を呼び出し元にコールバックする
1813+ callback.collect(layerImages, result[0]);
1814+ }
1815+
17371816 /**
17381817 * 伺か用PNG/PNAの出力.
17391818 */
Show on old repository browser