• R/O
  • HTTP
  • SSH
  • HTTPS

CharacterManaJ: Commit

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


Commit MetaInfo

Revisiond31bb4c8761d91249b86344b393c1b7c730235a1 (tree)
Time2019-01-16 20:49:32
Authorseraphy <seraphy@user...>
Commiterseraphy

Log Message

PSD出力時のRLE圧縮に対応。

および、ZIP出力時のファイル名も設定AppConfigに従うように修正

Change Summary

Incremental Difference

--- a/src/main/java/charactermanaj/graphics/io/ImageSaveHelper.java
+++ b/src/main/java/charactermanaj/graphics/io/ImageSaveHelper.java
@@ -50,6 +50,7 @@ import org.apache.tools.zip.ZipOutputStream;
5050
5151 import charactermanaj.graphics.io.OutputOption.PictureMode;
5252 import charactermanaj.graphics.io.OutputOption.ZoomRenderingType;
53+import charactermanaj.model.AppConfig;
5354 import charactermanaj.model.Layer;
5455 import charactermanaj.util.LocalizedMessageComboBoxRender;
5556 import charactermanaj.util.LocalizedResourcePropertyLoader;
@@ -606,8 +607,13 @@ public class ImageSaveHelper {
606607 */
607608 public void saveToZip(File outFile, Collection<LayerImage> layerImages, BufferedImage compositeImg)
608609 throws IOException {
610+ AppConfig appConfig = AppConfig.getInstance();
611+ String zipNameEncoding = appConfig.getZipNameEncoding();
612+
609613 ZipOutputStream zos = new ZipOutputStream(outFile);
610614 try {
615+ zos.setEncoding(zipNameEncoding);
616+
611617 if (layerImages != null) {
612618 for (LayerImage layerImage : layerImages) {
613619 String partsName = layerImage.getPartsName();
@@ -660,6 +666,11 @@ public class ImageSaveHelper {
660666 PSDCreator.LayerData layerData = new PSDCreator.LayerData(layerName, img);
661667 layerDatas.add(layerData);
662668 }
669+
670+ AppConfig appConfig = AppConfig.getInstance();
671+ PSDCreator.setUseRLECompression(appConfig.isUseRLECompressionForPSD()); // RLE圧縮の有無
672+
673+ // PSDデータ作成
663674 byte[] psdContents = PSDCreator.createPSD(layerDatas);
664675
665676 FileOutputStream fos = new FileOutputStream(outFile);
--- a/src/main/java/charactermanaj/graphics/io/PSDCreator.java
+++ b/src/main/java/charactermanaj/graphics/io/PSDCreator.java
@@ -7,7 +7,13 @@ import java.awt.image.WritableRaster;
77 import java.io.ByteArrayOutputStream;
88 import java.io.DataOutputStream;
99 import java.io.IOException;
10+import java.nio.ByteBuffer;
11+import java.nio.ByteOrder;
12+import java.util.ArrayList;
1013 import java.util.Collection;
14+import java.util.HashMap;
15+import java.util.List;
16+import java.util.Map;
1117
1218 /**
1319 * 複数レイヤー画像をPSD形式のデータとして作成する。
@@ -47,10 +53,67 @@ public final class PSDCreator {
4753 }
4854
4955 /**
50- * プライベートコンストラクタ
56+ * レイヤーとチャネルのペア
5157 */
52- private PSDCreator() {
53- super();
58+ static final class LayerChannelPair {
59+
60+ private final LayerData layerData;
61+
62+ private final int channel;
63+
64+ public LayerChannelPair(LayerData layerData, int channel) {
65+ this.layerData = layerData;
66+ this.channel = channel;
67+ }
68+
69+ public LayerData getLayerData() {
70+ return layerData;
71+ }
72+
73+ public int getChannel() {
74+ return channel;
75+ }
76+
77+ @Override
78+ public int hashCode() {
79+ final int prime = 31;
80+ int result = 1;
81+ result = prime * result + channel;
82+ result = prime * result + ((layerData == null) ? 0 : layerData.hashCode());
83+ return result;
84+ }
85+
86+ @Override
87+ public boolean equals(Object obj) {
88+ if (this == obj)
89+ return true;
90+ if (obj == null)
91+ return false;
92+ if (getClass() != obj.getClass())
93+ return false;
94+ LayerChannelPair other = (LayerChannelPair) obj;
95+ if (channel != other.channel)
96+ return false;
97+ if (layerData == null) {
98+ if (other.layerData != null)
99+ return false;
100+ } else if (!layerData.equals(other.layerData))
101+ return false;
102+ return true;
103+ }
104+ }
105+
106+ /**
107+ * RLEで圧縮するか?
108+ */
109+ private static boolean useRLECompression = true;
110+
111+ public static boolean isUseRLECompression() {
112+ return useRLECompression;
113+ }
114+
115+ public static void setUseRLECompression(boolean useRLECompression) {
116+ PSDCreator.useRLECompression = useRLECompression;
54117 }
55118
56119 /**
@@ -97,13 +160,8 @@ public final class PSDCreator {
97160 dos.write(layerMaskSection);
98161
99162 // 画像セクション
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- }
163+ byte[] pictureDatas = createPictureSection(cimg, width, height);
164+ dos.write(pictureDatas);
107165
108166 return bos.toByteArray();
109167 }
@@ -140,6 +198,7 @@ public final class PSDCreator {
140198
141199 short[] channels = { -1, 0, 1, 2 }; // ALPHA, RED, GREEN, BLUE
142200
201+ Map<LayerChannelPair, byte[]> channelDataMap = new HashMap<LayerChannelPair, byte[]>();
143202 for (LayerData layerData : layerDatas) {
144203 String layerName = layerData.getLayerName();
145204 BufferedImage image = layerData.getImage();
@@ -153,10 +212,48 @@ public final class PSDCreator {
153212
154213 dos.writeShort(channels.length);
155214
156- int rawSize = width * height;
215+ byte[][] channelsData = createChannels(image);
216+
157217 for (int channel = 0; channel < channels.length; channel++) {
218+ byte[] channelData = channelsData[channel];
219+ byte[] outChannelData;
220+
221+ if (useRLECompression) {
222+ // RLE圧縮
223+ // 行ごとにRLE圧縮する
224+ int bufsiz = 0;
225+ List<byte[]> rleRows = new ArrayList<byte[]>();
226+ for (int y = 0; y < height; y++) {
227+ byte[] rleRow = compressRLE(channelData, y * width, width);
228+ rleRows.add(rleRow);
229+ bufsiz += 2 + rleRow.length;
230+ }
231+
232+ ByteBuffer outbuf = ByteBuffer.allocate(bufsiz);
233+
234+ // 行ごとの圧縮サイズを格納
235+ for (byte[] rleRow : rleRows) {
236+ outbuf.putShort((short) rleRow.length);
237+ }
238+ // 行ごとに圧縮後データの格納
239+ for (byte[] rleRow : rleRows) {
240+ outbuf.put(rleRow);
241+ }
242+
243+ outChannelData = outbuf.array();
244+
245+ } else {
246+ // RAW (圧縮なし)
247+ outChannelData = channelData;
248+ }
249+
250+ // チャネルID (-1: alpha, 0: red, 1:green, 2:blue)
158251 dos.writeShort(channels[channel]);
159- dos.writeInt(2 + rawSize);
252+
253+ // チャネルのデータサイズ
254+ dos.writeInt(2 + outChannelData.length);
255+
256+ channelDataMap.put(new LayerChannelPair(layerData, channel), outChannelData);
160257 }
161258
162259 dos.write("8BIM".getBytes());
@@ -179,15 +276,12 @@ public final class PSDCreator {
179276 }
180277
181278 for (LayerData layerData : layerDatas) {
182- BufferedImage image = layerData.getImage();
183-
184- byte[][] channelsData = createChannels(image);
185-
186279 for (int channel = 0; channel < channels.length; channel++) {
187- dos.writeShort(0); // RAW
280+ byte[] outChannelData = channelDataMap.get(new LayerChannelPair(layerData, channel));
281+ assert outChannelData != null;
188282
189- byte[] channelData = channelsData[channel];
190- dos.write(channelData);
283+ dos.writeShort(useRLECompression ? 1 : 0); // 0:RAW 1:RLE 2..zip
284+ dos.write(outChannelData);
191285 }
192286 }
193287
@@ -305,4 +399,137 @@ public final class PSDCreator {
305399 }
306400 return cimg;
307401 }
402+
403+ /**
404+ * ARGB画像をRLE圧縮されたピクチャーセクションデータに変換する
405+ * @param img 画像
406+ * @param width 幅、画像とPSDヘッダと一致していること
407+ * @param height 高さ、画像とPSDヘッダと一致していること
408+ * @return RLE圧縮されたRGBA順チャンネルをつなげたデータ
409+ */
410+ private static byte[] createPictureSection(BufferedImage img, int width, int height) {
411+ byte[][] channels = createChannels(img);
412+
413+ assert width == img.getWidth();
414+ assert height == img.getHeight();
415+
416+ int[] channelMap = { 1, 2, 3, 0 }; // R, G, B, Aにマップ
417+
418+ ByteBuffer channelData;
419+ if (useRLECompression) {
420+ // RLE圧縮とサイズの計算
421+ int bufsiz = 2;
422+ List<byte[]> rows = new ArrayList<byte[]>();
423+ for (int channel = 0; channel < channels.length; channel++) {
424+ byte[] pixels = channels[channelMap[channel]];
425+ for (int y = 0; y < height; y++) {
426+ byte[] row = compressRLE(pixels, y * width, width);
427+ rows.add(row);
428+ bufsiz += 2 + row.length; // ラインごとのバイト数保存(16bit)とラインデータ分を加算
429+ }
430+ }
431+
432+ // RLE圧縮済みバッファ作成
433+ channelData = ByteBuffer.allocate(bufsiz);
434+ channelData.order(ByteOrder.BIG_ENDIAN);
435+
436+ channelData.putShort((short) 1); // RLE圧縮
437+
438+ // 各チャネルの各行ごとのデータ
439+ for (byte[] row : rows) {
440+ channelData.putShort((short) row.length);
441+ }
442+ for (byte[] row : rows) {
443+ channelData.put(row);
444+ }
445+
446+ } else {
447+ // RAWサイズの計算
448+ int bufsiz = 2;
449+ for (int channel = 0; channel < channels.length; channel++) {
450+ byte[] pixels = channels[channelMap[channel]];
451+ bufsiz += pixels.length;
452+ }
453+
454+ // RLE圧縮済みバッファ作成
455+ channelData = ByteBuffer.allocate(bufsiz);
456+ channelData.order(ByteOrder.BIG_ENDIAN);
457+
458+ channelData.putShort((short) 0); // RAW
459+
460+ for (int channel = 0; channel < channels.length; channel++) {
461+ byte[] pixels = channels[channelMap[channel]];
462+ channelData.put(pixels);
463+ }
464+ }
465+
466+ return channelData.array();
467+ }
468+
469+ /**
470+ * バイト配列をRLE圧縮して返す
471+ * http://www.snap-tck.com/room03/c02/comp/comp02.html
472+ * @param data 圧縮するバイト配列
473+ * @param offset 開始位置
474+ * @param length 長さ
475+ * @return RLE圧縮結果
476+ */
477+ public static byte[] compressRLE(byte[] data, int offset, int length) {
478+ ByteBuffer outbuf = ByteBuffer.allocate(length * 2); // ワーストケース
479+ ByteBuffer buf = ByteBuffer.wrap(data, offset, length);
480+ while (buf.hasRemaining()) {
481+ int ch = buf.get();
482+ // 不連続数を数える
483+ int count = 0;
484+ buf.mark();
485+ int prev = ch;
486+ while (buf.hasRemaining() && count < 128) {
487+ int ch2 = buf.get();
488+ if (prev == ch2) {
489+ break;
490+ }
491+ count++;
492+ prev = ch2;
493+ if (!buf.hasRemaining() && count < 128) {
494+ // 終端に達した場合は終端も不連続数と数える
495+ count++;
496+ break;
497+ }
498+ }
499+ buf.reset();
500+
501+ if (count > 0) {
502+ // 不連続数がある場合
503+ outbuf.put((byte) (count - 1));
504+ outbuf.put((byte) ch);
505+ while (--count > 0) {
506+ ch = buf.get();
507+ outbuf.put((byte) ch);
508+ }
509+
510+ } else {
511+ // 連続数を数える
512+ prev = ch;
513+ count = 1;
514+ while (buf.hasRemaining() && count < 128) {
515+ ch = buf.get();
516+ if (prev != ch) {
517+ buf.reset();
518+ break;
519+ }
520+ count++;
521+ buf.mark();
522+ }
523+ outbuf.put((byte) (-count + 1));
524+ outbuf.put((byte) prev);
525+ }
526+ }
527+
528+ outbuf.flip();
529+ int limit = outbuf.limit();
530+ byte[] array = outbuf.array();
531+ byte[] result = new byte[limit];
532+ System.arraycopy(array, 0, result, 0, limit);
533+ return result;
534+ }
308535 }
--- a/src/main/java/charactermanaj/model/AppConfig.java
+++ b/src/main/java/charactermanaj/model/AppConfig.java
@@ -1543,4 +1543,20 @@ public final class AppConfig {
15431543 propChangeSupport.firePropertyChange(USE_RECYCLE_BIN_IF_SUPPORTED, old, useRecycleBinIfSupported);
15441544 }
15451545 }
1546+
1547+ public boolean useRLECompressionForPSD = true;
1548+
1549+ private static final String USE_RLE_COMPRESSION_FOR_PSD = "useRLECompressionForPSD";
1550+
1551+ public boolean isUseRLECompressionForPSD() {
1552+ return useRLECompressionForPSD;
1553+ }
1554+
1555+ public void setUseRLECompressionForPSD(boolean useRLECompressionForPSD) {
1556+ boolean old = this.useRLECompressionForPSD;
1557+ if (old != useRLECompressionForPSD) {
1558+ this.useRLECompressionForPSD = useRLECompressionForPSD;
1559+ propChangeSupport.firePropertyChange(USE_RLE_COMPRESSION_FOR_PSD, old, useRLECompressionForPSD);
1560+ }
1561+ }
15461562 }
--- a/src/main/java/charactermanaj/model/io/CharacterDataZipFileWriter.java
+++ b/src/main/java/charactermanaj/model/io/CharacterDataZipFileWriter.java
@@ -20,12 +20,12 @@ public class CharacterDataZipFileWriter extends AbstractCharacterDataArchivedFil
2020 * Zipストリーム
2121 */
2222 protected ZipOutputStream zipOutStm;
23-
23+
2424 /**
2525 * ファイル名のエンコーディング
2626 */
2727 protected CharsetEncoder enc;
28-
28+
2929 /**
3030 * ルートコンテンツへのプレフィックス
3131 */
@@ -37,11 +37,11 @@ public class CharacterDataZipFileWriter extends AbstractCharacterDataArchivedFil
3737
3838 AppConfig appConfig = AppConfig.getInstance();
3939 String zipNameEncoding = appConfig.getZipNameEncoding();
40-
40+
4141 // コンストラクタにストリームではなくファイル名を指定することで、
4242 // 内部でランダムアクセスファイルを使うようになるためヘッダのCRCチェックの書き込み等で有利
4343 this.zipOutStm = new ZipOutputStream(tmpFile);
44-
44+
4545 // ファイル名の文字コードを設定する.
4646 // (JDKの標準のZipOutputStreamはUTF-8になるが、一般的にはMS932が多いため、Apache Antのものを借用し指定する.)
4747 this.enc = Charset.forName(zipNameEncoding).newEncoder();
@@ -67,7 +67,7 @@ public class CharacterDataZipFileWriter extends AbstractCharacterDataArchivedFil
6767 }
6868 this.rootPrefix = rootPrefix.trim();
6969 }
70-
70+
7171 public String getRootPrefix() {
7272 return rootPrefix;
7373 }
@@ -76,19 +76,19 @@ public class CharacterDataZipFileWriter extends AbstractCharacterDataArchivedFil
7676 protected void closeEntry() throws IOException {
7777 zipOutStm.closeEntry();
7878 }
79-
79+
8080 @Override
8181 protected OutputStream getOutputStream() throws IOException {
8282 return zipOutStm;
8383 }
84-
84+
8585 @Override
8686 protected void putNextEntry(String name, long lastModified)
8787 throws IOException {
8888
8989 // ルートプレフィックスをすべてのエントリの登録時に付与する.
9090 String fname = rootPrefix + name;
91-
91+
9292 // ファイル名がキャラクターセットに合致するか?
9393 checkName(fname);
9494
@@ -99,11 +99,11 @@ public class CharacterDataZipFileWriter extends AbstractCharacterDataArchivedFil
9999 }
100100 zipOutStm.putNextEntry(entry);
101101 }
102-
102+
103103 protected void internalClose() throws IOException {
104104 zipOutStm.close();
105105 }
106-
106+
107107 /**
108108 * ファイル名がエンコーディング可能であるかチェックする.<br>
109109 * @param name チェックする名前
--- a/src/main/resources/languages/appconfigdialog.xml
+++ b/src/main/resources/languages/appconfigdialog.xml
@@ -31,6 +31,7 @@ If the file already exists, the file is overwritten.]]></entry>
3131 <entry key="zipNameEncoding">03;ZIP File Encoding</entry>
3232 <entry key="partsColorGroupPattern">04;The judgment pattern of a color group.('@' is color group name)</entry>
3333 <entry key="enableAutoShrinkPanel">05;Auto shrink category panels</entry>
34+<entry key="useRLECompressionForPSD">06;Use RLE compression when outputting PSD</entry>
3435
3536 <entry key="mainFrameMaxWidth">10;Preview Max-Width</entry>
3637 <entry key="mainFrameMaxHeight">11;Preview Max-Height</entry>
--- a/src/main/resources/languages/appconfigdialog_ja.xml
+++ b/src/main/resources/languages/appconfigdialog_ja.xml
@@ -31,6 +31,7 @@
3131 <entry key="zipNameEncoding">03;ZIPファイルに格納されているファイル名のエンコーディング(csWindows31Jが標準)</entry>
3232 <entry key="partsColorGroupPattern">04;パーツ名からカラーグループを判定するパターン(正規表現)(@がカラーグループ名の場所になります.)</entry>
3333 <entry key="enableAutoShrinkPanel">05;自動的にパーツ選択パネルを縮小する.</entry>
34+<entry key="useRLECompressionForPSD">06;PSD出力時にRLE圧縮を使用する.</entry>
3435
3536 <entry key="mainFrameMaxWidth">10;プレビューの初期表示の最大幅</entry>
3637 <entry key="mainFrameMaxHeight">11;プレビューの初期表示の最大高さ</entry>
--- a/src/main/resources/languages/appconfigdialog_zh.xml
+++ b/src/main/resources/languages/appconfigdialog_zh.xml
@@ -30,6 +30,7 @@
3030 <entry key="zipNameEncoding">03;ZIP解码(默认为csWindows31J)</entry>
3131 <entry key="partsColorGroupPattern">04;以部件名称判定图案的颜色组(使用正则表达式)('@'后面为色组名)</entry>
3232 <entry key="enableAutoShrinkPanel">05;自动缩放项目栏</entry>
33+<entry key="useRLECompressionForPSD">06;输出PSD时使用RLE压缩</entry>
3334
3435 <entry key="mainFrameMaxWidth">10;预览图最大宽度</entry>
3536 <entry key="mainFrameMaxHeight">11;预览图最大高度</entry>
Show on old repository browser