8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Photoshop】psdファイルのフォーマットについて

Last updated at Posted at 2022-09-02

業務で psd ファイルを解析する必要があったのでその調査した内容についてまとめます。
ファイルフォーマット仕様に関してはAdobeが公開しているのでこちらの情報に準じます。

以下のような階層を持った psd ファイルを用意します。
スクリーンショット 2022-09-05 22.07.21.png

各データのセクションについて

データはいくつかのセクションに分かれており、
・File Header セクション
・Color Mode Data セクション
・Image Resources セクション
・Layer and Mask Information セクション
・Image Data セクション

に分かれています。
Color Mode Dataセクション、Image Resources セクション、Image Dataセクションに関しては今回は使用しなかったので割愛します。

データ型

この記事では各種データ型サイズは以下のように定義します。

型名 サイズ
byte 1byte
short 2byte
int 4byte
long 8byte
double 8byte

と定義します。
また、psd ファイルでは上記データ型は全てビッグエンディアンである事に注意してください。
また、文字列はPascal文字列とUnicode文字のどちらかが使われます。

PascalString

先頭の1バイトに文字列長が定義されている文字列です。
2バイト、もしくは4バイトでパディングすることがあります。
(パディング長はプロパティによって異なるので注意)

UnicodeString

先頭4バイトに文字数が含まれる ビッグエンディアンの2バイト文字列です。
この文字数はバイト数ではないので1文字2バイトで計算する必要がある点に注意してください。

Rect

レイヤーの描画範囲、マスクの範囲などで使われるデータ構造です。

データサイズ プロパティ名
int Top
int Left
int Bottom
int Right

File Header セクション

データサイズ プロパティ名
byte[4] Signature 常に8BPSの4文字が含まれる
short Version 常に 1 である事が期待される(ただしPSBファイルの場合は2である)
byte[6] Reserved 常に全て0である(使用されない)
short NumberOfChannels チャンネル数(範囲は 1~56 まで)
int Height キャンバスサイズ縦(範囲は 1~30,000 まで)
int Width キャンバスサイズ横(範囲は 1~30,000 まで)
short Depth ビット深度(1, 8, 16, 32)
short ColorMode カラーモード(定義は後述)
カラーモード定義

enum として定義すると下記のようになります。

enum ColorMode
{
    Bitmap = 0,
    Grayscale = 1,
    Indexed = 2,
    RGB = 3,
    CMYK = 4,
    Multichannel = 7,
    Duotone = 8,
    Lab = 9
}

今回はRGBしか使用しなかったので本記事ではカラーモードは RGB であるという前提で書きます。

Color Mode Data セクション

データサイズ プロパティ名
int Length セクション長さ

カラーモードが Indexed, Duotoneである場合に使用されるらしいのですが、
使用しなかったので内容がよくわからないので本記事ではスキップします。
(Indexed, Duotone以外はこのセクションのサイズは 0 です)

Image Resources セクション

データサイズ プロパティ名
int Length セクション長さ

下記のような構造をしたデータブロックが連続して続いているセクションです。
今回は使用しなかったのでこちらの詳細はスキップします。

データサイズ プロパティ名
byte[4] Signature 常に8BIMの4文字が含まれる
short ResourceID リソースのIDが含まれる。詳細はこちら
PascalString Name 2バイトでパディングされたPascal文字列。
null で合った場合も2バイト含まれる(中身は0)
int Length Data のサイズです
byte[Length] Data 各ResourceID別に格納されたデータのサイズ

Layer and Mask Information セクション

レイヤー情報が細かく定義されているセクションです。
この記事では主にこのセクションについての説明を行います。

データサイズ プロパティ名
int Length このセクション全体のサイズ
LayerInfo LayerInfo レイヤーの基本情報が含まれる
(次の項目で説明)
GlobalLayerMaskInformation GlobalLayerMaskInformation 使っていないので本記事では説明なし
LayerInfo
データサイズ プロパティ名
int Length このデータブロックのサイズ
short LayerCount レイヤー数 (※1
LayerRecord[] LayerRecords 各レイヤーの情報が含まれます。(次の項目で説明)
ChannelImageData[] ChannelImageData LayerRecordsの各レイヤーの画素情報が順番に定義されています。

※1) このレイヤー数は負の数である場合があります。
その場合は絶対値を使用します。
どうやら一番最初のレイヤーのアルファチャンネルに透明度データが含まれる場合、
負の数になるようです。

LayerRecord
データサイズ プロパティ名
Rect Rect レイヤーの範囲
short ChannelCount チャンネル数
Channel[ChannelCount] Channels チャンネル情報が含まれます。(次の項目で説明)
byte[4] BlendSignature ブレンドモードのシグネチャ(常に8BIM)
byte[4] BlendMode 4文字で表すブレンドモード
byte Opacity 透明度(0〜255)
byte Clipping クリッピングフラグ
byte Flags ビットフラグ
ビット0 「透明性を保護」にチェックが入っている場合 1
そうでない場合は 0
ビット1 レイヤーが非表示になっている場合は 1
表示状態である場合は 0
ビット2 廃止
ビット3 Photoshop 5.0 以降では 1、ビット 4 が有用な情報を持っているかどうかを示す。
ビット4 ドキュメントの外観とは無関係なピクセルデータであるか
byte Filler 常に 0
int ExtraDataFieldLength 次のデータブロックの長さ
LayerMaskAdjustmentLayerData Layer mask / adjustment layer data レイヤーマスクや調整レイヤーの情報が含まれているらしい。
あまり使っていないので割愛
LayerBlendingRangesData Layer blending ranges data 使っていないので割愛
PascalString LayerName レイヤー名 (※1
4バイトパディング
AdditionalLayerInformation[] AdditionalLayerInformations レイヤーに付属する情報
(レイヤー名やテキストレイヤーであればテキスト情報、レイヤーグループであるかどうかなど)

※1) このレイヤー名は32文字までしか含まれず、フルネームを取得したい場合はAdditionalLayerInformationから 'luni' タグに含まれる名前を取得したほうが良いです。

ブレンドモード
Key Blend Mode Name
pass pass 通過
norm normal 通常
diss dissolve ディザ合成
dark darken 比較(暗)
'mul ' multiply 乗算(mul + 半角スペース1文字なので注意)
idiv color burn 焼き込みカラー
lbrn linear burn 焼き込み(リニア)
dkCl darker color カラー比較(暗)
lite lighten 比較(明)
scrn screen スクリーン
'div ' color dodge 覆い焼きカラー
lddg linear dodge 覆い焼き(リニア)-加算
lgCl lighter color カラー比較(明)
over overlay オーバーレイ
sLit soft light ソフトライト
hLit hard light ハードライト
vLit vivid light ビビッドライト
lLit linear light リニアライト
pLit pin light ピンライト
hMix hard mix ハードミックス
diff difference 差の絶対値
smud exclusion 除外
fsub subtract 減算
fdiv divide 除算
'hue ' hue 色相 
'sat ' saturation  彩度
colr color  カラー
'lum ' luminosity 輝度

わかりやすくまとめているサイトがありました。
http://applehaguki.blog55.fc2.com/blog-entry-103.html

Channel

レイヤーのチャンネル情報
RGBモードであればR, G, B, Transparencyの4種類のチャンネル情報が1つのレイヤーに含まれている。

データサイズ プロパティ名
short ChannelId チャンネルID
int Length チャンネルデータサイズ
チャンネルID
enum ChannelId
{
    Red = 0,
    Green = 1,
    Blue = 2,
    Transparency = -1,
    UserLayerMask = -2,
    RealUserLayerMask = -3,
}
Channel Image Data部分
データサイズ プロパティ名
short Compression 圧縮形式
byte[Length] ImageData チャンネルの画素情報

実際のチャンネルデータ(画素情報)は LayerRecords を全て読み終えた後の領域に存在する点に注意
(便宜上別名にしていますが ChannelImageData と Channel は同じものです。)
コードとして実装するとこのような実装になります。

public class LayerInfo
{
    private LayerRecord[] m_layerRecords;
    // LayerInfo の情報を読み取る
    public void Read(IPsdReader reader)
    {
        // データブロックサイズを取得
        int length = reader.ReadInt();

        // レイヤー数を取得
        short layerCount = Math.Abs(reader.ReadShort());

        // LayerRecord を読み込む
        m_layerRecords = new Layer[layerCount];
        for (int i = 0; i < layerCount; ++i)
        {
            m_layerRecords[i] = new LayerRecord();
            m_layerRecords[i].Read(reader);
        }

        // 全 LayerRecord を読み込んだら ChannelImage を読み取る
        foreach (var layerRecord in m_layerRecords)
        {
            layerRecord.ReadChannelImage();
        }
    }
}

public class LayerRecord
{
    private readonly Channel[] m_channels;
    private readonly List<AdditionalLayerInformation> m_additionalLayerInformations = new();

    public void Read(IPsdReader reader)
    {
        Rect = Rectangle.Read(reader); // Rect 情報読み取り
        short channelCount = reader.ReadShort(); // チャンネル数
        m_channels = new Channel[channelCount];
        // チャンネル情報読み取り
        for (int i = 0; i < m_channels.Length; ++i)
        {
            m_channels[i] = new Channel();
            m_channels[i].Read(reader);
        }
        reader.Skip(4); // `8BIM`
        BlendMode = reader.ReadString(4);
        Opacity = reader.ReadByte();
        reader.Skip(1); // Clipping
        Flags = reader.ReadByte();
        reader.Skip(1); // Filler

        int extraDataLength = reader.ReadInt();

        // Layer mask / adjustment layer data
        int layerMaskAndAdjustmentDataLength = reader.ReadInt();
        reader.skip(layerMaskAndAdjustmentDataLength);

        // Layer blending ranges data.
        int layerBlendingDataLength = reader.ReadInt();
        reader.Skip(layerBlendingDataLength);

        LayerName = reader.ReadPascalString(padding: 4);

        // Additional Layer Information
        while (AdditionalLayerInformation.TryReadBlock(this, reader, out AdditionalLayerInformation info))
        {
            m_additionalLayerInformations.Add(info);
        }
    }

    // ImageData を読み取る
    public void ReadChannelImage(IPsdReader reader)
    {
        foreach (var channel in m_channels)
        {
            channel.ReadChannelImage(reader);
        }
    }
}


public class Channel
{
    public ChannelId ChannelId { get; private set; }
    private readonly int m_dataLength;
    public short Compression { get; private set; }
    public byte[] ImageData {get; private set; }

    public void Read(IPsdReader reader)
    {
        ChannelId = reader.ReadShort();
        m_dataLength = reader.ReadInt();
    }

    public void ReadChannelImageData(IPsdReader reader)
    {
        Compression = (Compression)reader.ReadShort();
        ImageData = reader.ReadBytes(m_dataLength - sizeof(Compression));
    }
}
注) IPsdReader というのはpsdファイルのストリームを持っていてint, short, long, doubleなどのデータ型に変換しながら読み進めることの出来る機能を持ったインターフェースであるという体で読んでください。
interface IPsdReader
{
    byte ReadByte();
    byte[] ReadBytes(long count);
    short ReadShort();
    int ReadInt();
    long ReadLong();
    double ReadDouble();
    void Skip(int byteCount);
    string ReadPascalString(int padding);
    string ReadUnicodeString();
}

AdditionalLayerInformation

レイヤーに付属する情報ブロック
4文字の識別情報(Code)と紐づいて定義されています。

共通情報

データサイズ プロパティ名
byte[4] Signature 常に 8BIM
byte[4] Code 識別情報
以下、識別情報別にそれぞれ定義されたデータを保持する
識別情報
Code 概要 補足
luni Unicode文字が含まれるブロック レイヤー名が含まれる
lnsr レイヤーIDで使われるレイヤー名?を持つブロック 使い方はよくわかってない
lyid レイヤーIDを持つブロック 使い方はよくわかってない
lsct レイヤーグループ情報を持つブロック Section Divider Settingと呼ばれている。
レイヤーグループとグループの終端となるレイヤー情報(</Layer group>というレイヤーがグループの終端に置かれる)に付加されている。
TySh Type Tool Objectを持つブロック テキストレイヤーの場合、テキストの情報が含まれる
識別情報別のデータ構造
luni
データサイズ
UnicodeString Unicode文字列
lnsr
データサイズ
int Id for the layer name
lyid
データサイズ
int layer id
lsct
データサイズ
int SectionDividerType エディタ上でフォルダが展開されているか or 閉じているか、またはレイヤーグループの終了地点であるかどうか
以下、Lengthが12以上の場合
byte[4] Signature 常に8BIM
byte[4] BlendModeKey ブレンドモード?
以下、Lengthが16以上の場合
int SubType 使っていないのでよくわからない
SectionDividerType

enum として定義すると以下のような感じ

// レイヤーグループの状態
enum SectionDividerType
{
    AnyOtherType = 0,
    // 展開されている
    OpenFolder = 1,
    // 閉じている
    ClosedFolder = 2,
    // グループの終了地点(</Layer Group>)である
    BoundingSectionDivider = 3,
}

この場合、SectionDividerSettings は以下のような値になります。
psdファイルフォーマット資料 (6).jpg

TypeToolObjectは説明が長くなるので別の記事で書く予定です。

レイヤー順番

レイヤーの順番は一番下から順になっており、この場合は

wood

</Layer group> (もしくは </Layer set> )

img

tape shadow
という順番になります。
構造を図にすると以下のような感じです。

psdファイルフォーマット資料 (5).jpg

Image Data セクション

このセクションには全てのレイヤーを統合した画像情報が含まれます。
色情報はChannelImageと異なり、Red, Green, Blueの順番で格納されているデータ

データブロックをスキップしたい場合

だいたいのデータブロックは先頭に4バイトのデータ長が含まれているので必要ないブロックに関しては取得したデータブロックサイズ分だけストリームを進めればスキップ可能

続く

8
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?