OSDN Git Service

0ff9346cc255b8d17df0751c9f5a50be8762d95e
[charactermanaj/CharacterManaJ.git] / src / main / java / charactermanaj / graphics / io / PSDCreator.java
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.nio.ByteBuffer;
11 import java.nio.ByteOrder;
12 import java.util.ArrayList;
13 import java.util.Collection;
14 import java.util.HashMap;
15 import java.util.List;
16 import java.util.Map;
17
18 /**
19  * 複数レイヤー画像をPSD形式のデータとして作成する。
20  * https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/
21  *
22  * @author seraphy
23  */
24 public final class PSDCreator {
25
26         /**
27          * レイヤーデータ
28          */
29         public static class LayerData {
30
31                 /**
32                  * レイヤー名
33                  */
34                 private String layerName;
35
36                 /**
37                  * レイヤーの画像(TYPE_INT_ARGB限定)
38                  */
39                 private BufferedImage image;
40
41                 public LayerData(String layerName, BufferedImage image) {
42                         this.layerName = layerName;
43                         this.image = image;
44                 }
45
46                 public String getLayerName() {
47                         return layerName;
48                 }
49
50                 public BufferedImage getImage() {
51                         return image;
52                 }
53         }
54
55         /**
56          * レイヤーとチャネルのペア
57          */
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;
117         }
118
119         /**
120          * レイヤーを指定してPSDデータを作成する
121          * @param layerDatas レイヤーのコレクション、順番に重ねられる
122          * @return PSDデータ
123          * @throws IOException
124          */
125         public static byte[] createPSD(Collection<LayerData> layerDatas) throws IOException {
126                 if (layerDatas == null) {
127                         throw new NullPointerException("layerDatas is required.");
128                 }
129                 if (layerDatas.isEmpty()) {
130                         throw new IllegalArgumentException("layerDatas must not be empty.");
131                 }
132
133                 BufferedImage cimg = createCompositeImage(layerDatas);
134                 int width = cimg.getWidth();
135                 int height = cimg.getHeight();
136
137                 ByteArrayOutputStream bos = new ByteArrayOutputStream();
138                 DataOutputStream dos = new DataOutputStream(bos);
139
140                 dos.write("8BPS".getBytes());
141                 dos.writeShort(1);
142                 dos.write(new byte[6]); // reserved 6bytes
143
144                 dos.writeShort(4); // argb
145
146                 dos.writeInt(height);
147                 dos.writeInt(width);
148
149                 int depth = 8;
150                 dos.writeShort(depth);
151
152                 dos.writeShort(3); // ColorMode=RGB(3)
153
154                 dos.writeInt(0); // カラーモードセクションなし
155                 dos.writeInt(0); // リソースセクションなし
156
157                 // レイヤーマスクセクション
158                 byte[] layerMaskSection = createLayerMaskSection(layerDatas);
159                 dos.writeInt(layerMaskSection.length);
160                 dos.write(layerMaskSection);
161
162                 // 画像セクション
163                 byte[] pictureDatas = createPictureSection(cimg, width, height);
164                 dos.write(pictureDatas);
165
166                 return bos.toByteArray();
167         }
168
169         /**
170          * レイヤーマスクセクションを作成する
171          * @param layerDatas
172          * @return
173          * @throws IOException
174          */
175         private static byte[] createLayerMaskSection(Collection<LayerData> layerDatas) throws IOException {
176                 ByteArrayOutputStream bos = new ByteArrayOutputStream();
177                 DataOutputStream dos = new DataOutputStream(bos);
178
179                 byte[] layerData = createLayerData(layerDatas);
180                 dos.writeInt(layerData.length);
181                 dos.write(layerData);
182
183                 return bos.toByteArray();
184         }
185
186         /**
187          * レイヤーデータの作成
188          * @param layerDatas
189          * @return
190          * @throws IOException
191          */
192         private static byte[] createLayerData(Collection<LayerData> layerDatas) throws IOException {
193                 ByteArrayOutputStream bos = new ByteArrayOutputStream();
194                 DataOutputStream dos = new DataOutputStream(bos);
195
196                 int numOfLayers = layerDatas.size();
197                 dos.writeShort(numOfLayers); // non pre-multiplied
198
199                 short[] channels = { -1, 0, 1, 2 }; // ALPHA, RED, GREEN, BLUE
200
201                 Map<LayerChannelPair, byte[]> channelDataMap = new HashMap<LayerChannelPair, byte[]>();
202                 for (LayerData layerData : layerDatas) {
203                         String layerName = layerData.getLayerName();
204                         BufferedImage image = layerData.getImage();
205                         int width = image.getWidth();
206                         int height = image.getHeight();
207
208                         dos.writeInt(0); // top
209                         dos.writeInt(0); // left
210                         dos.writeInt(height); // bottom
211                         dos.writeInt(width); // right
212
213                         dos.writeShort(channels.length);
214
215                         byte[][] channelsData = createChannels(image);
216
217                         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)
251                                 dos.writeShort(channels[channel]);
252
253                                 // チャネルのデータサイズ
254                                 dos.writeInt(2 + outChannelData.length);
255
256                                 channelDataMap.put(new LayerChannelPair(layerData, channel), outChannelData);
257                         }
258
259                         dos.write("8BIM".getBytes());
260                         dos.write("norm".getBytes());
261
262                         dos.write((byte) 255); // opacity
263                         dos.write((byte) 0); // clipping
264                         dos.write((byte) 0); // protection
265                         dos.write((byte) 0); // filler
266
267                         byte[] layerMaskData = createLayerMaskData();
268                         byte[] layerBlendingData = createLayerBlendingData();
269                         byte[] layerNameData = createLayerName(layerName);
270                         int lenOfAdditional = layerMaskData.length + layerBlendingData.length + layerNameData.length;
271
272                         dos.writeInt(lenOfAdditional);
273                         dos.write(layerMaskData);
274                         dos.write(layerBlendingData);
275                         dos.write(layerNameData);
276                 }
277
278                 for (LayerData layerData : layerDatas) {
279                         for (int channel = 0; channel < channels.length; channel++) {
280                                 byte[] outChannelData = channelDataMap.get(new LayerChannelPair(layerData, channel));
281                                 assert outChannelData != null;
282
283                                 dos.writeShort(useRLECompression ? 1 : 0); // 0:RAW 1:RLE 2..zip
284                                 dos.write(outChannelData);
285                         }
286                 }
287
288                 return bos.toByteArray();
289         }
290
291         /**
292          * 空のレイヤーマスクデータ作成
293          * @return
294          * @throws IOException
295          */
296         private static byte[] createLayerMaskData() throws IOException {
297                 ByteArrayOutputStream bos = new ByteArrayOutputStream();
298                 DataOutputStream dos = new DataOutputStream(bos);
299                 dos.writeInt(0);
300                 return bos.toByteArray();
301         }
302
303         /**
304          * 空のレイヤーブレンダーデータの作成
305          * @return
306          * @throws IOException
307          */
308         private static byte[] createLayerBlendingData() throws IOException {
309                 ByteArrayOutputStream bos = new ByteArrayOutputStream();
310                 DataOutputStream dos = new DataOutputStream(bos);
311                 dos.writeInt(0);
312                 return bos.toByteArray();
313         }
314
315         /**
316          * レイヤー名の作成
317          * @param layerName
318          * @return
319          * @throws IOException
320          */
321         private static byte[] createLayerName(String layerName) throws IOException {
322                 byte[] nameBuf = layerName.getBytes("UTF-8");
323                 int layerNameSize = 1 + nameBuf.length; // PASCAL文字列長 (16の倍数サイズ)
324                 int blockSize = (layerNameSize / 4) * 4 + ((layerNameSize % 4 > 0) ? 4 : 0);
325                 int paddingSize = blockSize - layerNameSize;
326
327                 ByteArrayOutputStream bos = new ByteArrayOutputStream();
328                 DataOutputStream dos = new DataOutputStream(bos);
329                 dos.write((byte) nameBuf.length);
330                 dos.write(nameBuf);
331                 dos.write(new byte[paddingSize]);
332                 return bos.toByteArray();
333         }
334
335         /**
336          * 32ビットARGB形式のBuffeedImageを受け取り、
337          * ARGBのbyte[][]配列に変換して返す。
338          * @param img イメージ
339          * @return チャネル別配列
340          */
341         private static byte[][] createChannels(BufferedImage img) {
342                 WritableRaster raster = img.getRaster();
343                 DataBufferInt buffer = (DataBufferInt) raster.getDataBuffer();
344                 int[] pixels = buffer.getData();
345
346                 int width = img.getWidth();
347                 int height = img.getHeight();
348                 int mx = width * height;
349                 byte[][] channels = new byte[4][mx];
350                 for (int idx = 0; idx < mx; idx++) {
351                         int argb = pixels[idx];
352
353                         int alpha = (argb >> 24) & 0xff;
354                         int red = (argb >> 16) & 0xff;
355                         int green = (argb >> 8) & 0xff;
356                         int blue = argb & 0xff;
357
358                         channels[0][idx] = (byte) alpha;
359                         channels[1][idx] = (byte) red;
360                         channels[2][idx] = (byte) green;
361                         channels[3][idx] = (byte) blue;
362                 }
363
364                 return channels;
365         }
366
367         /**
368          * レイヤーコレクションを重ねて1つの画像にして返す
369          * @param layerDatas レイヤーコレクション
370          * @return 重ね合わせた画像
371          */
372         private static BufferedImage createCompositeImage(Collection<LayerData> layerDatas) {
373                 int width = 0;
374                 int height = 0;
375                 for (LayerData layerData : layerDatas) {
376                         BufferedImage img = layerData.getImage();
377                         int w = img.getWidth();
378                         int h = img.getHeight();
379                         if (w > width) {
380                                 width = w;
381                         }
382                         if (h > height) {
383                                 height = h;
384                         }
385                 }
386
387                 BufferedImage cimg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
388
389                 Graphics2D g = cimg.createGraphics();
390                 try {
391                         for (LayerData layerData : layerDatas) {
392                                 BufferedImage img = layerData.getImage();
393                                 int w = img.getWidth();
394                                 int h = img.getHeight();
395                                 g.drawImage(img, 0, 0, w, h, 0, 0, w, h, null);
396                         }
397                 } finally {
398                         g.dispose();
399                 }
400                 return cimg;
401         }
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         }
535 }