Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      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
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     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.
     15  */
     16 
     17 package  com.android.hierarchyviewer.ui.util;
     18 
     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;
     29 
     30 /**
     31  * Writes PSD file.
     32  *
     33  * Supports only 8 bits, RGB images with 4 channels.
     34  */
     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;
     41 
     42     private final BufferedImage mMergedImage;
     43     private final Graphics2D mGraphics;
     44 
     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();
     51 
     52         mMergedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
     53         mGraphics = mMergedImage.createGraphics();
     54     }
     55 
     56     public void addLayer(String name, BufferedImage image, Point offset) {
     57         addLayer(name, image, offset, true);
     58     }
     59 
     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);
     63     }
     64 
     65     public void write(OutputStream stream) {
     66         mLayersMasksInfo.setLayersInfo(mLayersInfo);
     67 
     68         DataOutputStream out = new DataOutputStream(new BufferedOutputStream(stream));
     69         try {
     70             mHeader.write(out);
     71             out.flush();
     72 
     73             mColorMode.write(out);
     74             mImageResources.write(out);
     75             mLayersMasksInfo.write(out);
     76             mLayersInfo.write(out);
     77             out.flush();
     78 
     79             mLayersInfo.writeImageData(out);
     80             out.flush();
     81 
     82             writeImage(mMergedImage, out, false);
     83             out.flush();
     84         } catch (IOException e) {
     85             e.printStackTrace();
     86         } finally {
     87             try {
     88                 out.close();
     89             } catch (IOException e) {
     90                 e.printStackTrace();
     91             }
     92         }
     93     }
     94 
     95     private static void writeImage(BufferedImage image, DataOutputStream out, boolean split)
     96             throws IOException {
     97 
     98         if (!split) out.writeShort(0);
     99 
    100         int width = image.getWidth();
    101         int height = image.getHeight();
    102 
    103         final int length = width * height;
    104         int[] pixels = new int[length];
    105 
    106         image.getData().getDataElements(0, 0, width, height, pixels);
    107 
    108         byte[] a = new byte[length];
    109         byte[] r = new byte[length];
    110         byte[] g = new byte[length];
    111         byte[] b = new byte[length];
    112 
    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);
    119         }
    120 
    121         if (split) out.writeShort(0);
    122         if (split) out.write(a);
    123         if (split) out.writeShort(0);
    124         out.write(r);
    125         if (split) out.writeShort(0);
    126         out.write(g);
    127         if (split) out.writeShort(0);
    128         out.write(b);
    129         if (!split) out.write(a);
    130     }
    131 
    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;
    142 
    143         final byte[] mSignature = "8BPS".getBytes();
    144         final short mVersion = 1;
    145         final byte[] mReserved = new byte[6];
    146         final short mChannelCount = 4;
    147         final int mHeight;
    148         final int mWidth;
    149         final short mDepth = 8;
    150         final short mMode = MODE_RGB;
    151 
    152         Header(int width, int height) {
    153             mWidth = width;
    154             mHeight = height;
    155         }
    156 
    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);
    166         }
    167     }
    168 
    169     // Unused at the moment
    170     @SuppressWarnings({"UnusedDeclaration"})
    171     static class ColorMode {
    172         final int mLength = 0;
    173 
    174         void write(DataOutputStream out) throws IOException {
    175             out.writeInt(mLength);
    176         }
    177     }
    178 
    179     // Unused at the moment
    180     @SuppressWarnings({"UnusedDeclaration"})
    181     static class ImageResources {
    182         static final short RESOURCE_RESOLUTION_INFO = 0x03ED;
    183 
    184         int mLength = 0;
    185 
    186         final byte[] mSignature = "8BIM".getBytes();
    187         final short mResourceId = RESOURCE_RESOLUTION_INFO;
    188 
    189         final short mPad = 0;
    190 
    191         final int mDataLength = 16;
    192 
    193         final short mHorizontalDisplayUnit = 0x48; // 72 dpi
    194         final int mHorizontalResolution = 1;
    195         final short mWidthDisplayUnit = 1;
    196 
    197         final short mVerticalDisplayUnit = 0x48; // 72 dpi
    198         final int mVerticalResolution = 1;
    199         final short mHeightDisplayUnit = 1;
    200 
    201         ImageResources() {
    202             mLength = mSignature.length;
    203             mLength += 2;
    204             mLength += 2;
    205             mLength += 4;
    206             mLength += 8;
    207             mLength += 8;
    208         }
    209 
    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);
    222         }
    223     }
    224 
    225     @SuppressWarnings({"UnusedDeclaration"})
    226     static class LayersMasksInfo {
    227         int mMiscLength;
    228         int mLayerInfoLength;
    229 
    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;
    235         }
    236 
    237         void write(DataOutputStream out) throws IOException {
    238             out.writeInt(mMiscLength);
    239             out.writeInt(mLayerInfoLength);
    240         }
    241     }
    242 
    243     @SuppressWarnings({"UnusedDeclaration"})
    244     static class LayersInfo {
    245         final List<Layer> mLayers = new ArrayList<Layer>();
    246 
    247         void addLayer(String name, BufferedImage image, Point offset, boolean visible) {
    248             mLayers.add(new Layer(name, image, offset, visible));
    249         }
    250 
    251         int getLength() {
    252             int length = 2;
    253             for (Layer layer : mLayers) {
    254                 length += layer.getLength();
    255             }
    256             return length;
    257         }
    258 
    259         void write(DataOutputStream out) throws IOException {
    260             out.writeShort((short) -mLayers.size());
    261             for (Layer layer : mLayers) {
    262                 layer.write(out);
    263             }
    264         }
    265 
    266         void writeImageData(DataOutputStream out) throws IOException {
    267             for (Layer layer : mLayers) {
    268                 layer.writeImageData(out);
    269             }
    270             // Global layer mask info length
    271             out.writeInt(0);
    272         }
    273     }
    274 
    275     @SuppressWarnings({"UnusedDeclaration"})
    276     static class Layer {
    277         static final byte OPACITY_TRANSPARENT = 0x0;
    278         static final byte OPACITY_OPAQUE = (byte) 0xFF;
    279 
    280         static final byte CLIPPING_BASE = 0x0;
    281         static final byte CLIPPING_NON_BASE = 0x1;
    282 
    283         static final byte FLAG_TRANSPARENCY_PROTECTED = 0x1;
    284         static final byte FLAG_INVISIBLE = 0x2;
    285 
    286         final int mTop;
    287         final int mLeft;
    288         final int mBottom;
    289         final int mRight;
    290 
    291         final short mChannelCount = 4;
    292         final Channel[] mChannelInfo = new Channel[mChannelCount];
    293 
    294         final byte[] mBlendSignature = "8BIM".getBytes();
    295         final byte[] mBlendMode = "norm".getBytes();
    296 
    297         final byte mOpacity = OPACITY_OPAQUE;
    298         final byte mClipping = CLIPPING_BASE;
    299         byte mFlags = 0x0;
    300         final byte mFiller = 0x0;
    301 
    302         int mExtraSize = 4 + 4;
    303 
    304         final int mMaskDataLength = 0;
    305         final int mBlendRangeDataLength = 0;
    306 
    307         final byte[] mName;
    308 
    309         final byte[] mLayerExtraSignature = "8BIM".getBytes();
    310         final byte[] mLayerExtraKey = "luni".getBytes();
    311         int mLayerExtraLength;
    312         final String mOriginalName;
    313 
    314         private BufferedImage mImage;
    315 
    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;
    320 
    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);
    325 
    326             mTop = offset.y;
    327             mLeft = offset.x;
    328             mBottom = offset.y + height;
    329             mRight = offset.x + width;
    330 
    331             mOriginalName = name;
    332             byte[] data = name.getBytes();
    333 
    334             try {
    335                 mLayerExtraLength = 4 + mOriginalName.getBytes("UTF-16").length;
    336             } catch (UnsupportedEncodingException e) {
    337                 e.printStackTrace();
    338             }
    339 
    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);
    343 
    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);
    348                 mName = data;
    349             } else {
    350                 mName = nameData;
    351             }
    352             mExtraSize += mName.length;
    353             mExtraSize += mLayerExtraLength + 4 + mLayerExtraKey.length +
    354                     mLayerExtraSignature.length;
    355 
    356             mImage = image;
    357 
    358             if (!visible) {
    359                 mFlags |= FLAG_INVISIBLE;
    360             }
    361         }
    362 
    363         int getLength() {
    364             int length = 4 * 4 + 2;
    365 
    366             for (Channel channel : mChannelInfo) {
    367                 length += channel.getLength();
    368             }
    369 
    370             length += mBlendSignature.length;
    371             length += mBlendMode.length;
    372             length += 4;
    373             length += 4;
    374             length += mExtraSize;
    375 
    376             return length;
    377         }
    378 
    379         void write(DataOutputStream out) throws IOException {
    380             out.writeInt(mTop);
    381             out.writeInt(mLeft);
    382             out.writeInt(mBottom);
    383             out.writeInt(mRight);
    384 
    385             out.writeShort(mChannelCount);
    386             for (Channel channel : mChannelInfo) {
    387                 channel.write(out);
    388             }
    389 
    390             out.write(mBlendSignature);
    391             out.write(mBlendMode);
    392 
    393             out.write(mOpacity);
    394             out.write(mClipping);
    395             out.write(mFlags);
    396             out.write(mFiller);
    397 
    398             out.writeInt(mExtraSize);
    399             out.writeInt(mMaskDataLength);
    400 
    401             out.writeInt(mBlendRangeDataLength);
    402 
    403             out.write(mName);
    404 
    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"));
    410         }
    411 
    412         void writeImageData(DataOutputStream out) throws IOException {
    413             writeImage(mImage, out, true);
    414         }
    415     }
    416 
    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;
    424 
    425         final short mId;
    426         final int mDataLength;
    427 
    428         Channel(short id, int dataLength) {
    429             mId = id;
    430             mDataLength = dataLength + 2;
    431         }
    432 
    433         int getLength() {
    434             return 2 + 4 + mDataLength;
    435         }
    436 
    437         void write(DataOutputStream out) throws IOException {
    438             out.writeShort(mId);
    439             out.writeInt(mDataLength);
    440         }
    441     }
    442 }
    443