diff options
Diffstat (limited to 'hierarchyviewer/src/com/android/hierarchyviewer/ui/util/PsdFile.java')
-rw-r--r-- | hierarchyviewer/src/com/android/hierarchyviewer/ui/util/PsdFile.java | 442 |
1 files changed, 442 insertions, 0 deletions
diff --git a/hierarchyviewer/src/com/android/hierarchyviewer/ui/util/PsdFile.java b/hierarchyviewer/src/com/android/hierarchyviewer/ui/util/PsdFile.java new file mode 100644 index 000000000..3768e41b5 --- /dev/null +++ b/hierarchyviewer/src/com/android/hierarchyviewer/ui/util/PsdFile.java @@ -0,0 +1,442 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.hierarchyviewer.ui.util; + +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.image.BufferedImage; +import java.io.BufferedOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; + +/** + * Writes PSD file. + * + * Supports only 8 bits, RGB images with 4 channels. + */ +public class PsdFile { + private final Header mHeader; + private final ColorMode mColorMode; + private final ImageResources mImageResources; + private final LayersMasksInfo mLayersMasksInfo; + private final LayersInfo mLayersInfo; + + private final BufferedImage mMergedImage; + private final Graphics2D mGraphics; + + public PsdFile(int width, int height) { + mHeader = new Header(width, height); + mColorMode = new ColorMode(); + mImageResources = new ImageResources(); + mLayersMasksInfo = new LayersMasksInfo(); + mLayersInfo = new LayersInfo(); + + mMergedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + mGraphics = mMergedImage.createGraphics(); + } + + public void addLayer(String name, BufferedImage image, Point offset) { + addLayer(name, image, offset, true); + } + + public void addLayer(String name, BufferedImage image, Point offset, boolean visible) { + mLayersInfo.addLayer(name, image, offset, visible); + if (visible) mGraphics.drawImage(image, null, offset.x, offset.y); + } + + public void write(OutputStream stream) { + mLayersMasksInfo.setLayersInfo(mLayersInfo); + + DataOutputStream out = new DataOutputStream(new BufferedOutputStream(stream)); + try { + mHeader.write(out); + out.flush(); + + mColorMode.write(out); + mImageResources.write(out); + mLayersMasksInfo.write(out); + mLayersInfo.write(out); + out.flush(); + + mLayersInfo.writeImageData(out); + out.flush(); + + writeImage(mMergedImage, out, false); + out.flush(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private static void writeImage(BufferedImage image, DataOutputStream out, boolean split) + throws IOException { + + if (!split) out.writeShort(0); + + int width = image.getWidth(); + int height = image.getHeight(); + + final int length = width * height; + int[] pixels = new int[length]; + + image.getData().getDataElements(0, 0, width, height, pixels); + + byte[] a = new byte[length]; + byte[] r = new byte[length]; + byte[] g = new byte[length]; + byte[] b = new byte[length]; + + for (int i = 0; i < length; i++) { + final int pixel = pixels[i]; + a[i] = (byte) ((pixel >> 24) & 0xFF); + r[i] = (byte) ((pixel >> 16) & 0xFF); + g[i] = (byte) ((pixel >> 8) & 0xFF); + b[i] = (byte) (pixel & 0xFF); + } + + if (split) out.writeShort(0); + if (split) out.write(a); + if (split) out.writeShort(0); + out.write(r); + if (split) out.writeShort(0); + out.write(g); + if (split) out.writeShort(0); + out.write(b); + if (!split) out.write(a); + } + + @SuppressWarnings({"UnusedDeclaration"}) + static class Header { + static final short MODE_BITMAP = 0; + static final short MODE_GRAYSCALE = 1; + static final short MODE_INDEXED = 2; + static final short MODE_RGB = 3; + static final short MODE_CMYK = 4; + static final short MODE_MULTI_CHANNEL = 7; + static final short MODE_DUOTONE = 8; + static final short MODE_LAB = 9; + + final byte[] mSignature = "8BPS".getBytes(); + final short mVersion = 1; + final byte[] mReserved = new byte[6]; + final short mChannelCount = 4; + final int mHeight; + final int mWidth; + final short mDepth = 8; + final short mMode = MODE_RGB; + + Header(int width, int height) { + mWidth = width; + mHeight = height; + } + + void write(DataOutputStream out) throws IOException { + out.write(mSignature); + out.writeShort(mVersion); + out.write(mReserved); + out.writeShort(mChannelCount); + out.writeInt(mHeight); + out.writeInt(mWidth); + out.writeShort(mDepth); + out.writeShort(mMode); + } + } + + // Unused at the moment + @SuppressWarnings({"UnusedDeclaration"}) + static class ColorMode { + final int mLength = 0; + + void write(DataOutputStream out) throws IOException { + out.writeInt(mLength); + } + } + + // Unused at the moment + @SuppressWarnings({"UnusedDeclaration"}) + static class ImageResources { + static final short RESOURCE_RESOLUTION_INFO = 0x03ED; + + int mLength = 0; + + final byte[] mSignature = "8BIM".getBytes(); + final short mResourceId = RESOURCE_RESOLUTION_INFO; + + final short mPad = 0; + + final int mDataLength = 16; + + final short mHorizontalDisplayUnit = 0x48; // 72 dpi + final int mHorizontalResolution = 1; + final short mWidthDisplayUnit = 1; + + final short mVerticalDisplayUnit = 0x48; // 72 dpi + final int mVerticalResolution = 1; + final short mHeightDisplayUnit = 1; + + ImageResources() { + mLength = mSignature.length; + mLength += 2; + mLength += 2; + mLength += 4; + mLength += 8; + mLength += 8; + } + + void write(DataOutputStream out) throws IOException { + out.writeInt(mLength); + out.write(mSignature); + out.writeShort(mResourceId); + out.writeShort(mPad); + out.writeInt(mDataLength); + out.writeShort(mHorizontalDisplayUnit); + out.writeInt(mHorizontalResolution); + out.writeShort(mWidthDisplayUnit); + out.writeShort(mVerticalDisplayUnit); + out.writeInt(mVerticalResolution); + out.writeShort(mHeightDisplayUnit); + } + } + + @SuppressWarnings({"UnusedDeclaration"}) + static class LayersMasksInfo { + int mMiscLength; + int mLayerInfoLength; + + void setLayersInfo(LayersInfo layersInfo) { + mLayerInfoLength = layersInfo.getLength(); + // Round to the next multiple of 2 + if ((mLayerInfoLength & 0x1) == 0x1) mLayerInfoLength++; + mMiscLength = mLayerInfoLength + 8; + } + + void write(DataOutputStream out) throws IOException { + out.writeInt(mMiscLength); + out.writeInt(mLayerInfoLength); + } + } + + @SuppressWarnings({"UnusedDeclaration"}) + static class LayersInfo { + final List<Layer> mLayers = new ArrayList<Layer>(); + + void addLayer(String name, BufferedImage image, Point offset, boolean visible) { + mLayers.add(new Layer(name, image, offset, visible)); + } + + int getLength() { + int length = 2; + for (Layer layer : mLayers) { + length += layer.getLength(); + } + return length; + } + + void write(DataOutputStream out) throws IOException { + out.writeShort((short) -mLayers.size()); + for (Layer layer : mLayers) { + layer.write(out); + } + } + + void writeImageData(DataOutputStream out) throws IOException { + for (Layer layer : mLayers) { + layer.writeImageData(out); + } + // Global layer mask info length + out.writeInt(0); + } + } + + @SuppressWarnings({"UnusedDeclaration"}) + static class Layer { + static final byte OPACITY_TRANSPARENT = 0x0; + static final byte OPACITY_OPAQUE = (byte) 0xFF; + + static final byte CLIPPING_BASE = 0x0; + static final byte CLIPPING_NON_BASE = 0x1; + + static final byte FLAG_TRANSPARENCY_PROTECTED = 0x1; + static final byte FLAG_INVISIBLE = 0x2; + + final int mTop; + final int mLeft; + final int mBottom; + final int mRight; + + final short mChannelCount = 4; + final Channel[] mChannelInfo = new Channel[mChannelCount]; + + final byte[] mBlendSignature = "8BIM".getBytes(); + final byte[] mBlendMode = "norm".getBytes(); + + final byte mOpacity = OPACITY_OPAQUE; + final byte mClipping = CLIPPING_BASE; + byte mFlags = 0x0; + final byte mFiller = 0x0; + + int mExtraSize = 4 + 4; + + final int mMaskDataLength = 0; + final int mBlendRangeDataLength = 0; + + final byte[] mName; + + final byte[] mLayerExtraSignature = "8BIM".getBytes(); + final byte[] mLayerExtraKey = "luni".getBytes(); + int mLayerExtraLength; + final String mOriginalName; + + private BufferedImage mImage; + + Layer(String name, BufferedImage image, Point offset, boolean visible) { + final int height = image.getHeight(); + final int width = image.getWidth(); + final int length = width * height; + + mChannelInfo[0] = new Channel(Channel.ID_ALPHA, length); + mChannelInfo[1] = new Channel(Channel.ID_RED, length); + mChannelInfo[2] = new Channel(Channel.ID_GREEN, length); + mChannelInfo[3] = new Channel(Channel.ID_BLUE, length); + + mTop = offset.y; + mLeft = offset.x; + mBottom = offset.y + height; + mRight = offset.x + width; + + mOriginalName = name; + byte[] data = name.getBytes(); + + try { + mLayerExtraLength = 4 + mOriginalName.getBytes("UTF-16").length; + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + final byte[] nameData = new byte[data.length + 1]; + nameData[0] = (byte) (data.length & 0xFF); + System.arraycopy(data, 0, nameData, 1, data.length); + + // This could be done in the same pass as above + if (nameData.length % 4 != 0) { + data = new byte[nameData.length + 4 - (nameData.length % 4)]; + System.arraycopy(nameData, 0, data, 0, nameData.length); + mName = data; + } else { + mName = nameData; + } + mExtraSize += mName.length; + mExtraSize += mLayerExtraLength + 4 + mLayerExtraKey.length + + mLayerExtraSignature.length; + + mImage = image; + + if (!visible) { + mFlags |= FLAG_INVISIBLE; + } + } + + int getLength() { + int length = 4 * 4 + 2; + + for (Channel channel : mChannelInfo) { + length += channel.getLength(); + } + + length += mBlendSignature.length; + length += mBlendMode.length; + length += 4; + length += 4; + length += mExtraSize; + + return length; + } + + void write(DataOutputStream out) throws IOException { + out.writeInt(mTop); + out.writeInt(mLeft); + out.writeInt(mBottom); + out.writeInt(mRight); + + out.writeShort(mChannelCount); + for (Channel channel : mChannelInfo) { + channel.write(out); + } + + out.write(mBlendSignature); + out.write(mBlendMode); + + out.write(mOpacity); + out.write(mClipping); + out.write(mFlags); + out.write(mFiller); + + out.writeInt(mExtraSize); + out.writeInt(mMaskDataLength); + + out.writeInt(mBlendRangeDataLength); + + out.write(mName); + + out.write(mLayerExtraSignature); + out.write(mLayerExtraKey); + out.writeInt(mLayerExtraLength); + out.writeInt(mOriginalName.length() + 1); + out.write(mOriginalName.getBytes("UTF-16")); + } + + void writeImageData(DataOutputStream out) throws IOException { + writeImage(mImage, out, true); + } + } + + @SuppressWarnings({"UnusedDeclaration"}) + static class Channel { + static final short ID_RED = 0; + static final short ID_GREEN = 1; + static final short ID_BLUE = 2; + static final short ID_ALPHA = -1; + static final short ID_LAYER_MASK = -2; + + final short mId; + final int mDataLength; + + Channel(short id, int dataLength) { + mId = id; + mDataLength = dataLength + 2; + } + + int getLength() { + return 2 + 4 + mDataLength; + } + + void write(DataOutputStream out) throws IOException { + out.writeShort(mId); + out.writeInt(mDataLength); + } + } +} |