2 * Copyright (C) 2010 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.hierarchyviewer.ui.util;
19 import java.awt.Graphics2D;
20 import java.awt.Point;
21 import java.awt.image.BufferedImage;
22 import java.io.BufferedOutputStream;
23 import java.io.DataOutputStream;
24 import java.io.IOException;
25 import java.io.OutputStream;
26 import java.io.UnsupportedEncodingException;
27 import java.util.ArrayList;
28 import java.util.List;
33 * Supports only 8 bits, RGB images with 4 channels.
35 public class PsdFile {
36 private final Header mHeader;
37 private final ColorMode mColorMode;
38 private final ImageResources mImageResources;
39 private final LayersMasksInfo mLayersMasksInfo;
40 private final LayersInfo mLayersInfo;
42 private final BufferedImage mMergedImage;
43 private final Graphics2D mGraphics;
45 public PsdFile(int width, int height) {
46 mHeader = new Header(width, height);
47 mColorMode = new ColorMode();
48 mImageResources = new ImageResources();
49 mLayersMasksInfo = new LayersMasksInfo();
50 mLayersInfo = new LayersInfo();
52 mMergedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
53 mGraphics = mMergedImage.createGraphics();
56 public void addLayer(String name, BufferedImage image, Point offset) {
57 addLayer(name, image, offset, true);
60 public void addLayer(String name, BufferedImage image, Point offset, boolean visible) {
61 mLayersInfo.addLayer(name, image, offset, visible);
62 if (visible) mGraphics.drawImage(image, null, offset.x, offset.y);
65 public void write(OutputStream stream) {
66 mLayersMasksInfo.setLayersInfo(mLayersInfo);
68 DataOutputStream out = new DataOutputStream(new BufferedOutputStream(stream));
73 mColorMode.write(out);
74 mImageResources.write(out);
75 mLayersMasksInfo.write(out);
76 mLayersInfo.write(out);
79 mLayersInfo.writeImageData(out);
82 writeImage(mMergedImage, out, false);
84 } catch (IOException e) {
89 } catch (IOException e) {
95 private static void writeImage(BufferedImage image, DataOutputStream out, boolean split)
98 if (!split) out.writeShort(0);
100 int width = image.getWidth();
101 int height = image.getHeight();
103 final int length = width * height;
104 int[] pixels = new int[length];
106 image.getData().getDataElements(0, 0, width, height, pixels);
108 byte[] a = new byte[length];
109 byte[] r = new byte[length];
110 byte[] g = new byte[length];
111 byte[] b = new byte[length];
113 for (int i = 0; i < length; i++) {
114 final int pixel = pixels[i];
115 a[i] = (byte) ((pixel >> 24) & 0xFF);
116 r[i] = (byte) ((pixel >> 16) & 0xFF);
117 g[i] = (byte) ((pixel >> 8) & 0xFF);
118 b[i] = (byte) (pixel & 0xFF);
121 if (split) out.writeShort(0);
122 if (split) out.write(a);
123 if (split) out.writeShort(0);
125 if (split) out.writeShort(0);
127 if (split) out.writeShort(0);
129 if (!split) out.write(a);
132 @SuppressWarnings({"UnusedDeclaration"})
133 static class Header {
134 static final short MODE_BITMAP = 0;
135 static final short MODE_GRAYSCALE = 1;
136 static final short MODE_INDEXED = 2;
137 static final short MODE_RGB = 3;
138 static final short MODE_CMYK = 4;
139 static final short MODE_MULTI_CHANNEL = 7;
140 static final short MODE_DUOTONE = 8;
141 static final short MODE_LAB = 9;
143 final byte[] mSignature = "8BPS".getBytes();
144 final short mVersion = 1;
145 final byte[] mReserved = new byte[6];
146 final short mChannelCount = 4;
149 final short mDepth = 8;
150 final short mMode = MODE_RGB;
152 Header(int width, int height) {
157 void write(DataOutputStream out) throws IOException {
158 out.write(mSignature);
159 out.writeShort(mVersion);
160 out.write(mReserved);
161 out.writeShort(mChannelCount);
162 out.writeInt(mHeight);
163 out.writeInt(mWidth);
164 out.writeShort(mDepth);
165 out.writeShort(mMode);
169 // Unused at the moment
170 @SuppressWarnings({"UnusedDeclaration"})
171 static class ColorMode {
172 final int mLength = 0;
174 void write(DataOutputStream out) throws IOException {
175 out.writeInt(mLength);
179 // Unused at the moment
180 @SuppressWarnings({"UnusedDeclaration"})
181 static class ImageResources {
182 static final short RESOURCE_RESOLUTION_INFO = 0x03ED;
186 final byte[] mSignature = "8BIM".getBytes();
187 final short mResourceId = RESOURCE_RESOLUTION_INFO;
189 final short mPad = 0;
191 final int mDataLength = 16;
193 final short mHorizontalDisplayUnit = 0x48; // 72 dpi
194 final int mHorizontalResolution = 1;
195 final short mWidthDisplayUnit = 1;
197 final short mVerticalDisplayUnit = 0x48; // 72 dpi
198 final int mVerticalResolution = 1;
199 final short mHeightDisplayUnit = 1;
202 mLength = mSignature.length;
210 void write(DataOutputStream out) throws IOException {
211 out.writeInt(mLength);
212 out.write(mSignature);
213 out.writeShort(mResourceId);
214 out.writeShort(mPad);
215 out.writeInt(mDataLength);
216 out.writeShort(mHorizontalDisplayUnit);
217 out.writeInt(mHorizontalResolution);
218 out.writeShort(mWidthDisplayUnit);
219 out.writeShort(mVerticalDisplayUnit);
220 out.writeInt(mVerticalResolution);
221 out.writeShort(mHeightDisplayUnit);
225 @SuppressWarnings({"UnusedDeclaration"})
226 static class LayersMasksInfo {
228 int mLayerInfoLength;
230 void setLayersInfo(LayersInfo layersInfo) {
231 mLayerInfoLength = layersInfo.getLength();
232 // Round to the next multiple of 2
233 if ((mLayerInfoLength & 0x1) == 0x1) mLayerInfoLength++;
234 mMiscLength = mLayerInfoLength + 8;
237 void write(DataOutputStream out) throws IOException {
238 out.writeInt(mMiscLength);
239 out.writeInt(mLayerInfoLength);
243 @SuppressWarnings({"UnusedDeclaration"})
244 static class LayersInfo {
245 final List<Layer> mLayers = new ArrayList<Layer>();
247 void addLayer(String name, BufferedImage image, Point offset, boolean visible) {
248 mLayers.add(new Layer(name, image, offset, visible));
253 for (Layer layer : mLayers) {
254 length += layer.getLength();
259 void write(DataOutputStream out) throws IOException {
260 out.writeShort((short) -mLayers.size());
261 for (Layer layer : mLayers) {
266 void writeImageData(DataOutputStream out) throws IOException {
267 for (Layer layer : mLayers) {
268 layer.writeImageData(out);
270 // Global layer mask info length
275 @SuppressWarnings({"UnusedDeclaration"})
277 static final byte OPACITY_TRANSPARENT = 0x0;
278 static final byte OPACITY_OPAQUE = (byte) 0xFF;
280 static final byte CLIPPING_BASE = 0x0;
281 static final byte CLIPPING_NON_BASE = 0x1;
283 static final byte FLAG_TRANSPARENCY_PROTECTED = 0x1;
284 static final byte FLAG_INVISIBLE = 0x2;
291 final short mChannelCount = 4;
292 final Channel[] mChannelInfo = new Channel[mChannelCount];
294 final byte[] mBlendSignature = "8BIM".getBytes();
295 final byte[] mBlendMode = "norm".getBytes();
297 final byte mOpacity = OPACITY_OPAQUE;
298 final byte mClipping = CLIPPING_BASE;
300 final byte mFiller = 0x0;
302 int mExtraSize = 4 + 4;
304 final int mMaskDataLength = 0;
305 final int mBlendRangeDataLength = 0;
309 final byte[] mLayerExtraSignature = "8BIM".getBytes();
310 final byte[] mLayerExtraKey = "luni".getBytes();
311 int mLayerExtraLength;
312 final String mOriginalName;
314 private BufferedImage mImage;
316 Layer(String name, BufferedImage image, Point offset, boolean visible) {
317 final int height = image.getHeight();
318 final int width = image.getWidth();
319 final int length = width * height;
321 mChannelInfo[0] = new Channel(Channel.ID_ALPHA, length);
322 mChannelInfo[1] = new Channel(Channel.ID_RED, length);
323 mChannelInfo[2] = new Channel(Channel.ID_GREEN, length);
324 mChannelInfo[3] = new Channel(Channel.ID_BLUE, length);
328 mBottom = offset.y + height;
329 mRight = offset.x + width;
331 mOriginalName = name;
332 byte[] data = name.getBytes();
335 mLayerExtraLength = 4 + mOriginalName.getBytes("UTF-16").length;
336 } catch (UnsupportedEncodingException e) {
340 final byte[] nameData = new byte[data.length + 1];
341 nameData[0] = (byte) (data.length & 0xFF);
342 System.arraycopy(data, 0, nameData, 1, data.length);
344 // This could be done in the same pass as above
345 if (nameData.length % 4 != 0) {
346 data = new byte[nameData.length + 4 - (nameData.length % 4)];
347 System.arraycopy(nameData, 0, data, 0, nameData.length);
352 mExtraSize += mName.length;
353 mExtraSize += mLayerExtraLength + 4 + mLayerExtraKey.length +
354 mLayerExtraSignature.length;
359 mFlags |= FLAG_INVISIBLE;
364 int length = 4 * 4 + 2;
366 for (Channel channel : mChannelInfo) {
367 length += channel.getLength();
370 length += mBlendSignature.length;
371 length += mBlendMode.length;
374 length += mExtraSize;
379 void write(DataOutputStream out) throws IOException {
382 out.writeInt(mBottom);
383 out.writeInt(mRight);
385 out.writeShort(mChannelCount);
386 for (Channel channel : mChannelInfo) {
390 out.write(mBlendSignature);
391 out.write(mBlendMode);
394 out.write(mClipping);
398 out.writeInt(mExtraSize);
399 out.writeInt(mMaskDataLength);
401 out.writeInt(mBlendRangeDataLength);
405 out.write(mLayerExtraSignature);
406 out.write(mLayerExtraKey);
407 out.writeInt(mLayerExtraLength);
408 out.writeInt(mOriginalName.length() + 1);
409 out.write(mOriginalName.getBytes("UTF-16"));
412 void writeImageData(DataOutputStream out) throws IOException {
413 writeImage(mImage, out, true);
417 @SuppressWarnings({"UnusedDeclaration"})
418 static class Channel {
419 static final short ID_RED = 0;
420 static final short ID_GREEN = 1;
421 static final short ID_BLUE = 2;
422 static final short ID_ALPHA = -1;
423 static final short ID_LAYER_MASK = -2;
426 final int mDataLength;
428 Channel(short id, int dataLength) {
430 mDataLength = dataLength + 2;
434 return 2 + 4 + mDataLength;
437 void write(DataOutputStream out) throws IOException {
439 out.writeInt(mDataLength);