OSDN Git Service

PSD出力時のRLE圧縮に対応。
authorseraphy <seraphy@users.osdn.me>
Wed, 16 Jan 2019 11:49:32 +0000 (20:49 +0900)
committerseraphy <seraphy@users.osdn.me>
Wed, 16 Jan 2019 11:49:32 +0000 (20:49 +0900)
および、ZIP出力時のファイル名も設定AppConfigに従うように修正

src/main/java/charactermanaj/graphics/io/ImageSaveHelper.java
src/main/java/charactermanaj/graphics/io/PSDCreator.java
src/main/java/charactermanaj/model/AppConfig.java
src/main/java/charactermanaj/model/io/CharacterDataZipFileWriter.java
src/main/resources/languages/appconfigdialog.xml
src/main/resources/languages/appconfigdialog_ja.xml
src/main/resources/languages/appconfigdialog_zh.xml

index 200e493..86228f6 100644 (file)
@@ -50,6 +50,7 @@ import org.apache.tools.zip.ZipOutputStream;
 
 import charactermanaj.graphics.io.OutputOption.PictureMode;
 import charactermanaj.graphics.io.OutputOption.ZoomRenderingType;
+import charactermanaj.model.AppConfig;
 import charactermanaj.model.Layer;
 import charactermanaj.util.LocalizedMessageComboBoxRender;
 import charactermanaj.util.LocalizedResourcePropertyLoader;
@@ -606,8 +607,13 @@ public class ImageSaveHelper {
         */
        public void saveToZip(File outFile, Collection<LayerImage> layerImages, BufferedImage compositeImg)
                        throws IOException {
+               AppConfig appConfig = AppConfig.getInstance();
+               String zipNameEncoding = appConfig.getZipNameEncoding();
+
                ZipOutputStream zos = new ZipOutputStream(outFile);
                try {
+                       zos.setEncoding(zipNameEncoding);
+                       
                        if (layerImages != null) {
                                for (LayerImage layerImage : layerImages) {
                                        String partsName = layerImage.getPartsName();
@@ -660,6 +666,11 @@ public class ImageSaveHelper {
                        PSDCreator.LayerData layerData = new PSDCreator.LayerData(layerName, img);
                        layerDatas.add(layerData);
                }
+
+               AppConfig appConfig = AppConfig.getInstance();
+               PSDCreator.setUseRLECompression(appConfig.isUseRLECompressionForPSD()); // RLE圧縮の有無
+
+               // PSDデータ作成
                byte[] psdContents = PSDCreator.createPSD(layerDatas);
 
                FileOutputStream fos = new FileOutputStream(outFile);
index f73d42b..0ff9346 100644 (file)
@@ -7,7 +7,13 @@ import java.awt.image.WritableRaster;
 import java.io.ByteArrayOutputStream;
 import java.io.DataOutputStream;
 import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * 複数レイヤー画像をPSD形式のデータとして作成する。
@@ -47,10 +53,67 @@ public final class PSDCreator {
        }
 
        /**
-        * ã\83\97ã\83©ã\82¤ã\83\99ã\83¼ã\83\88ã\82³ã\83³ã\82¹ã\83\88ã\83©ã\82¯ã\82¿
+        * ã\83¬ã\82¤ã\83¤ã\83¼ã\81¨ã\83\81ã\83£ã\83\8dã\83«ã\81®ã\83\9aã\82¢
         */
-       private PSDCreator() {
-               super();
+       static final class LayerChannelPair {
+
+               private final LayerData layerData;
+
+               private final int channel;
+
+               public LayerChannelPair(LayerData layerData, int channel) {
+                       this.layerData = layerData;
+                       this.channel = channel;
+               }
+
+               public LayerData getLayerData() {
+                       return layerData;
+               }
+
+               public int getChannel() {
+                       return channel;
+               }
+
+               @Override
+               public int hashCode() {
+                       final int prime = 31;
+                       int result = 1;
+                       result = prime * result + channel;
+                       result = prime * result + ((layerData == null) ? 0 : layerData.hashCode());
+                       return result;
+               }
+
+               @Override
+               public boolean equals(Object obj) {
+                       if (this == obj)
+                               return true;
+                       if (obj == null)
+                               return false;
+                       if (getClass() != obj.getClass())
+                               return false;
+                       LayerChannelPair other = (LayerChannelPair) obj;
+                       if (channel != other.channel)
+                               return false;
+                       if (layerData == null) {
+                               if (other.layerData != null)
+                                       return false;
+                       } else if (!layerData.equals(other.layerData))
+                               return false;
+                       return true;
+               }
+       }
+
+       /**
+        * RLEで圧縮するか?
+        */
+       private static boolean useRLECompression = true;
+
+       public static boolean isUseRLECompression() {
+               return useRLECompression;
+       }
+
+       public static void setUseRLECompression(boolean useRLECompression) {
+               PSDCreator.useRLECompression = useRLECompression;
        }
 
        /**
@@ -97,13 +160,8 @@ public final class PSDCreator {
                dos.write(layerMaskSection);
 
                // 画像セクション
-               dos.writeShort(0); // RAW
-               byte[][] channelDatas = createChannels(cimg);
-               int[] channelMap = { 1, 2, 3, 0 }; // R, G, B, Aにマップ
-               for (int channel = 0; channel < channelMap.length; channel++) {
-                       byte[] channelData = channelDatas[channelMap[channel]];
-                       dos.write(channelData);
-               }
+               byte[] pictureDatas = createPictureSection(cimg, width, height);
+               dos.write(pictureDatas);
 
                return bos.toByteArray();
        }
@@ -140,6 +198,7 @@ public final class PSDCreator {
 
                short[] channels = { -1, 0, 1, 2 }; // ALPHA, RED, GREEN, BLUE
 
+               Map<LayerChannelPair, byte[]> channelDataMap = new HashMap<LayerChannelPair, byte[]>();
                for (LayerData layerData : layerDatas) {
                        String layerName = layerData.getLayerName();
                        BufferedImage image = layerData.getImage();
@@ -153,10 +212,48 @@ public final class PSDCreator {
 
                        dos.writeShort(channels.length);
 
-                       int rawSize = width * height;
+                       byte[][] channelsData = createChannels(image);
+
                        for (int channel = 0; channel < channels.length; channel++) {
+                               byte[] channelData = channelsData[channel];
+                               byte[] outChannelData;
+
+                               if (useRLECompression) {
+                                       // RLE圧縮
+                                       // 行ごとにRLE圧縮する
+                                       int bufsiz = 0;
+                                       List<byte[]> rleRows = new ArrayList<byte[]>();
+                                       for (int y = 0; y < height; y++) {
+                                               byte[] rleRow = compressRLE(channelData, y * width, width);
+                                               rleRows.add(rleRow);
+                                               bufsiz += 2 + rleRow.length;
+                                       }
+
+                                       ByteBuffer outbuf = ByteBuffer.allocate(bufsiz);
+
+                                       // 行ごとの圧縮サイズを格納
+                                       for (byte[] rleRow : rleRows) {
+                                               outbuf.putShort((short) rleRow.length);
+                                       }
+                                       // 行ごとに圧縮後データの格納
+                                       for (byte[] rleRow : rleRows) {
+                                               outbuf.put(rleRow);
+                                       }
+
+                                       outChannelData = outbuf.array();
+
+                               } else {
+                                       // RAW (圧縮なし)
+                                       outChannelData = channelData;
+                               }
+
+                               // チャネルID (-1: alpha, 0: red, 1:green, 2:blue)
                                dos.writeShort(channels[channel]);
-                               dos.writeInt(2 + rawSize);
+
+                               // チャネルのデータサイズ
+                               dos.writeInt(2 + outChannelData.length);
+
+                               channelDataMap.put(new LayerChannelPair(layerData, channel), outChannelData);
                        }
 
                        dos.write("8BIM".getBytes());
@@ -179,15 +276,12 @@ public final class PSDCreator {
                }
 
                for (LayerData layerData : layerDatas) {
-                       BufferedImage image = layerData.getImage();
-
-                       byte[][] channelsData = createChannels(image);
-
                        for (int channel = 0; channel < channels.length; channel++) {
-                               dos.writeShort(0); // RAW
+                               byte[] outChannelData = channelDataMap.get(new LayerChannelPair(layerData, channel));
+                               assert outChannelData != null;
 
-                               byte[] channelData = channelsData[channel];
-                               dos.write(channelData);
+                               dos.writeShort(useRLECompression ? 1 : 0); // 0:RAW 1:RLE 2..zip
+                               dos.write(outChannelData);
                        }
                }
 
@@ -305,4 +399,137 @@ public final class PSDCreator {
                }
                return cimg;
        }
+
+       /**
+        * ARGB画像をRLE圧縮されたピクチャーセクションデータに変換する
+        * @param img 画像
+        * @param width 幅、画像とPSDヘッダと一致していること
+        * @param height 高さ、画像とPSDヘッダと一致していること
+        * @return RLE圧縮されたRGBA順チャンネルをつなげたデータ
+        */
+       private static byte[] createPictureSection(BufferedImage img, int width, int height) {
+               byte[][] channels = createChannels(img);
+
+               assert width == img.getWidth();
+               assert height == img.getHeight();
+
+               int[] channelMap = { 1, 2, 3, 0 }; // R, G, B, Aにマップ
+
+               ByteBuffer channelData;
+               if (useRLECompression) {
+                       // RLE圧縮とサイズの計算
+                       int bufsiz = 2;
+                       List<byte[]> rows = new ArrayList<byte[]>();
+                       for (int channel = 0; channel < channels.length; channel++) {
+                               byte[] pixels = channels[channelMap[channel]];
+                               for (int y = 0; y < height; y++) {
+                                       byte[] row = compressRLE(pixels, y * width, width);
+                                       rows.add(row);
+                                       bufsiz += 2 + row.length; // ラインごとのバイト数保存(16bit)とラインデータ分を加算
+                               }
+                       }
+
+                       // RLE圧縮済みバッファ作成
+                       channelData = ByteBuffer.allocate(bufsiz);
+                       channelData.order(ByteOrder.BIG_ENDIAN);
+
+                       channelData.putShort((short) 1); // RLE圧縮
+
+                       // 各チャネルの各行ごとのデータ
+                       for (byte[] row : rows) {
+                               channelData.putShort((short) row.length);
+                       }
+                       for (byte[] row : rows) {
+                               channelData.put(row);
+                       }
+
+               } else {
+                       // RAWサイズの計算
+                       int bufsiz = 2;
+                       for (int channel = 0; channel < channels.length; channel++) {
+                               byte[] pixels = channels[channelMap[channel]];
+                               bufsiz += pixels.length;
+                       }
+
+                       // RLE圧縮済みバッファ作成
+                       channelData = ByteBuffer.allocate(bufsiz);
+                       channelData.order(ByteOrder.BIG_ENDIAN);
+
+                       channelData.putShort((short) 0); // RAW
+
+                       for (int channel = 0; channel < channels.length; channel++) {
+                               byte[] pixels = channels[channelMap[channel]];
+                               channelData.put(pixels);
+                       }
+               }
+
+               return channelData.array();
+       }
+
+       /**
+        * バイト配列をRLE圧縮して返す
+        *  http://www.snap-tck.com/room03/c02/comp/comp02.html
+        * @param data 圧縮するバイト配列
+        * @param offset 開始位置
+        * @param length 長さ
+        * @return RLE圧縮結果
+        */
+       public static byte[] compressRLE(byte[] data, int offset, int length) {
+               ByteBuffer outbuf = ByteBuffer.allocate(length * 2); // ワーストケース
+               ByteBuffer buf = ByteBuffer.wrap(data, offset, length);
+               while (buf.hasRemaining()) {
+                       int ch = buf.get();
+                       // 不連続数を数える
+                       int count = 0;
+                       buf.mark();
+                       int prev = ch;
+                       while (buf.hasRemaining() && count < 128) {
+                               int ch2 = buf.get();
+                               if (prev == ch2) {
+                                       break;
+                               }
+                               count++;
+                               prev = ch2;
+                               if (!buf.hasRemaining() && count < 128) {
+                                       // 終端に達した場合は終端も不連続数と数える
+                                       count++;
+                                       break;
+                               }
+                       }
+                       buf.reset();
+
+                       if (count > 0) {
+                               // 不連続数がある場合
+                               outbuf.put((byte) (count - 1));
+                               outbuf.put((byte) ch);
+                               while (--count > 0) {
+                                       ch = buf.get();
+                                       outbuf.put((byte) ch);
+                               }
+
+                       } else {
+                               // 連続数を数える
+                               prev = ch;
+                               count = 1;
+                               while (buf.hasRemaining() && count < 128) {
+                                       ch = buf.get();
+                                       if (prev != ch) {
+                                               buf.reset();
+                                               break;
+                                       }
+                                       count++;
+                                       buf.mark();
+                               }
+                               outbuf.put((byte) (-count + 1));
+                               outbuf.put((byte) prev);
+                       }
+               }
+
+               outbuf.flip();
+               int limit = outbuf.limit();
+               byte[] array = outbuf.array();
+               byte[] result = new byte[limit];
+               System.arraycopy(array, 0, result, 0, limit);
+               return result;
+       }
 }
index e205f13..22217c9 100644 (file)
@@ -1543,4 +1543,20 @@ public final class AppConfig {
                        propChangeSupport.firePropertyChange(USE_RECYCLE_BIN_IF_SUPPORTED, old, useRecycleBinIfSupported);
                }
        }
+
+       public boolean useRLECompressionForPSD = true;
+
+       private static final String USE_RLE_COMPRESSION_FOR_PSD = "useRLECompressionForPSD";
+
+       public boolean isUseRLECompressionForPSD() {
+               return useRLECompressionForPSD;
+       }
+
+       public void setUseRLECompressionForPSD(boolean useRLECompressionForPSD) {
+               boolean old = this.useRLECompressionForPSD;
+               if (old != useRLECompressionForPSD) {
+                       this.useRLECompressionForPSD = useRLECompressionForPSD;
+                       propChangeSupport.firePropertyChange(USE_RLE_COMPRESSION_FOR_PSD, old, useRLECompressionForPSD);
+               }
+       }
 }
index 20222d6..aba9a75 100644 (file)
@@ -20,12 +20,12 @@ public class CharacterDataZipFileWriter extends AbstractCharacterDataArchivedFil
         * Zipストリーム
         */
        protected ZipOutputStream zipOutStm;
-       
+
        /**
         * ファイル名のエンコーディング
         */
        protected CharsetEncoder enc;
-       
+
        /**
         * ルートコンテンツへのプレフィックス
         */
@@ -37,11 +37,11 @@ public class CharacterDataZipFileWriter extends AbstractCharacterDataArchivedFil
 
                AppConfig appConfig = AppConfig.getInstance();
                String zipNameEncoding = appConfig.getZipNameEncoding();
-               
+
                // コンストラクタにストリームではなくファイル名を指定することで、
                // 内部でランダムアクセスファイルを使うようになるためヘッダのCRCチェックの書き込み等で有利
                this.zipOutStm = new ZipOutputStream(tmpFile);
-               
+
                // ファイル名の文字コードを設定する.
                // (JDKの標準のZipOutputStreamはUTF-8になるが、一般的にはMS932が多いため、Apache Antのものを借用し指定する.)
                this.enc = Charset.forName(zipNameEncoding).newEncoder();
@@ -67,7 +67,7 @@ public class CharacterDataZipFileWriter extends AbstractCharacterDataArchivedFil
                }
                this.rootPrefix = rootPrefix.trim();
        }
-       
+
        public String getRootPrefix() {
                return rootPrefix;
        }
@@ -76,19 +76,19 @@ public class CharacterDataZipFileWriter extends AbstractCharacterDataArchivedFil
        protected void closeEntry() throws IOException {
                zipOutStm.closeEntry();
        }
-       
+
        @Override
        protected OutputStream getOutputStream() throws IOException {
                return zipOutStm;
        }
-       
+
        @Override
        protected void putNextEntry(String name, long lastModified)
                        throws IOException {
 
                 // ルートプレフィックスをすべてのエントリの登録時に付与する.
                String fname = rootPrefix + name;
-               
+
                // ファイル名がキャラクターセットに合致するか?
                checkName(fname);
 
@@ -99,11 +99,11 @@ public class CharacterDataZipFileWriter extends AbstractCharacterDataArchivedFil
                }
                zipOutStm.putNextEntry(entry);
        }
-       
+
        protected void internalClose() throws IOException {
                zipOutStm.close();
        }
-       
+
        /**
         * ファイル名がエンコーディング可能であるかチェックする.<br>
         * @param name チェックする名前
index d6c0a3d..caf7f18 100644 (file)
@@ -31,6 +31,7 @@ If the file already exists, the file is overwritten.]]></entry>
 <entry key="zipNameEncoding">03;ZIP File Encoding</entry>
 <entry key="partsColorGroupPattern">04;The judgment pattern of a color group.('@' is color group name)</entry>
 <entry key="enableAutoShrinkPanel">05;Auto shrink category panels</entry>
+<entry key="useRLECompressionForPSD">06;Use RLE compression when outputting PSD</entry>
 
 <entry key="mainFrameMaxWidth">10;Preview Max-Width</entry>
 <entry key="mainFrameMaxHeight">11;Preview Max-Height</entry>
index fd68865..8e4a600 100644 (file)
@@ -31,6 +31,7 @@
 <entry key="zipNameEncoding">03;ZIPファイルに格納されているファイル名のエンコーディング(csWindows31Jが標準)</entry>
 <entry key="partsColorGroupPattern">04;パーツ名からカラーグループを判定するパターン(正規表現)(@がカラーグループ名の場所になります.)</entry>
 <entry key="enableAutoShrinkPanel">05;自動的にパーツ選択パネルを縮小する.</entry>
+<entry key="useRLECompressionForPSD">06;PSD出力時にRLE圧縮を使用する.</entry>
 
 <entry key="mainFrameMaxWidth">10;プレビューの初期表示の最大幅</entry>
 <entry key="mainFrameMaxHeight">11;プレビューの初期表示の最大高さ</entry>
index c3c233b..6192c09 100644 (file)
@@ -30,6 +30,7 @@
 <entry key="zipNameEncoding">03;ZIP解码(默认为csWindows31J)</entry>
 <entry key="partsColorGroupPattern">04;以部件名称判定图案的颜色组(使用正则表达式)('@'后面为色组名)</entry>
 <entry key="enableAutoShrinkPanel">05;自动缩放项目栏</entry>
+<entry key="useRLECompressionForPSD">06;输出PSD时使用RLE压缩</entry>
 
 <entry key="mainFrameMaxWidth">10;预览图最大宽度</entry>
 <entry key="mainFrameMaxHeight">11;预览图最大高度</entry>