2009年03月17日

X3FファイルJpeg展開コード

Sigma DP1が出力するファイル形式はX3Fという、なんだかよく分からないファイル形式になっています。その為、ちょっと画像をチェックしたい場合でも、 Sigma Photo ProというRAW現像ソフトを起動しなければなりません。はっきり言ってかなり面倒です。

そんな訳で、X3FファイルからJpegを抜き出すコードをC#で書いてみました。このJpegはRAWを現像したものではなく、カメラ内部で自動生成されるプレビュー用のJpegです。webでX3Fファイルの仕様を解析しているサイトがあったので、非常に簡単にコードはかけました。ライセンスは BSD Licenceで。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.IO;

namespace X3F_Viewer
{
    /// <summary>
    /// X3Fファイルを読み込むクラス。DP1専用。
    /// 
    /// 履歴
    /// 2009/02/27  ver0.1
    /// 2009/03/17  ver0.2  ToString()をoverride。
    /// 
    /// 参考文献
    /// 1. http://rubyist.g.hatena.ne.jp/takuma104/20080505/1210006450
    /// 2. http://anotherm8.exblog.jp/7838817/
    /// 3. http://www.photofo.com/x3f-raw-format/
    /// 4. http://www.photofo.com/downloads/x3f-raw-format.pdf
    /// </summary>
    class X3F
    {
        /// <summary>
        /// 画像の形式。
        /// </summary>
        public enum ImageType {
            /// <summary>
            /// 通常サイズのJpeg画像。
            /// </summary>
            jpeg = 0,
            /// <summary>
            /// サムネイル画像。
            /// </summary>
            thumbnail = 4
        }
        /// <summary>
        /// X3Fに記録された撮影情報の構造体。
        /// </summary>
        public struct PhotoInfo
        {
            public string AEMODE;
            public string AFMODE;
            public string AP_DESC;
            public string APERTURE;
            public string BRACKET;
            public string BURST;
            public string CAMMANUF;
            public string CAMMODEL;
            public string CAMNAME;
            public string CAMSERIAL;
            public string DRIVE;
            public string EXPCOMP;
            public string EXPNET;
            public string EXPTIME;
            public string FIRMVERS;
            public string FLASH;
            public string FLENGTH;
            public string FLEQ35MM;
            public string FOCUS;
            public string IMAGERBOARDID;
            public string IMAGERTEMP;
            public string ISO;
            public string LENSARANGE;
            public string LENSFRANGE;
            public string LENSMODEL;
            public string PMODE;
            public string RESOLUTION;
            public string SENSORID;
            public string SH_DESC;
            public string SHUTTER;
            public string TIME;
            public string WB_DESC;

            public override string ToString()
            {
                StringBuilder sb = new StringBuilder();
                sb.Append("AEMODE:"); sb.AppendLine(this.AEMODE);
                sb.Append("AFMODE:"); sb.AppendLine(this.AFMODE);
                sb.Append("AP_DESC:"); sb.AppendLine(this.AP_DESC);
                sb.Append("APERTURE:"); sb.AppendLine(this.APERTURE);
                sb.Append("BRACKET:"); sb.AppendLine(this.BRACKET);
                sb.Append("BURST:"); sb.AppendLine(this.BURST);
                sb.Append("CAMMANUF:"); sb.AppendLine(this.CAMMANUF);
                sb.Append("CAMMODEL:"); sb.AppendLine(this.CAMMODEL);
                sb.Append("CAMNAME:"); sb.AppendLine(this.CAMNAME);
                sb.Append("CAMSERIAL:"); sb.AppendLine(this.CAMSERIAL);
                sb.Append("DRIVE:"); sb.AppendLine(this.DRIVE);
                sb.Append("EXPCOMP:"); sb.AppendLine(this.EXPCOMP);
                sb.Append("EXPNET:"); sb.AppendLine(this.EXPNET);
                sb.Append("EXPTIME:"); sb.AppendLine(this.EXPTIME);
                sb.Append("FIRMVERS:"); sb.AppendLine(this.FIRMVERS);
                sb.Append("FLASH:"); sb.AppendLine(this.FLASH);
                sb.Append("FLENGTH:"); sb.AppendLine(this.FLENGTH);
                sb.Append("FLEQ35MM:"); sb.AppendLine(this.FLEQ35MM);
                sb.Append("FOCUS:"); sb.AppendLine(this.FOCUS);
                sb.Append("IMAGERBOARDID:"); sb.AppendLine(this.IMAGERBOARDID);
                sb.Append("IMAGERTEMP:"); sb.AppendLine(this.IMAGERTEMP);
                sb.Append("ISO:"); sb.AppendLine(this.ISO);
                sb.Append("LENSARANGE:"); sb.AppendLine(this.LENSARANGE);
                sb.Append("LENSFRANGE:"); sb.AppendLine(this.LENSFRANGE);
                sb.Append("LENSMODEL:"); sb.AppendLine(this.LENSMODEL);
                sb.Append("PMODE:"); sb.AppendLine(this.PMODE);
                sb.Append("RESOLUTION:"); sb.AppendLine(this.RESOLUTION);
                sb.Append("SENSORID:"); sb.AppendLine(this.SENSORID);
                sb.Append("SH_DESC:"); sb.AppendLine(this.SH_DESC);
                sb.Append("SHUTTER:"); sb.AppendLine(this.SHUTTER);
                sb.Append("TIME:"); sb.AppendLine(this.TIME);
                sb.Append("WB_DESC:"); sb.AppendLine(this.WB_DESC);
                return sb.ToString();
            }
        }
        /// <summary>
        /// X3FファイルのDirectoryDataの構造体。
        /// </summary>
        protected struct DirectoryData
        {
            /// <summary>
            /// 画像データのoffset。
            /// </summary>
            public Int32 offset;
            /// <summary>
            /// 画像データのサイズ。
            /// </summary>
            public Int32 size;
            /// <summary>
            /// データの種類。
            /// </summary>
            public string tag;
        }


        /// <summary>
        /// X3Fファイルのパス。
        /// </summary>
        public string Path
        {
            get { return this.path; }
        }
        /// <summary>
        /// X3Fファイルの撮影情報。
        /// </summary>
        public PhotoInfo Info
        {
            get { return this.photoInfo; }
        }

        /// <summary>
        /// X3Fファイルのパス。
        /// </summary>
        private string path;
        /// <summary>
        /// X3Fの撮影情報。
        /// </summary>
        private PhotoInfo photoInfo;
        /// <summary>
        /// X3Fファイル内のディレクトリ情報を格納した配列。
        /// </summary>
        private DirectoryData[] directoryDataArray;

        /// <summary>
        /// コンストラクタ。
        /// X3Fファイルの基本情報を取得する。
        /// </summary>
        /// <param name="path">X3Fファイルのパス。</param>
        public X3F(string path)
        {
            this.path = path;
            using (BinaryReader binaryReader = new BinaryReader(File.OpenRead(path)))
            {
                this.directoryDataArray = this.GetDirectoryIndex(binaryReader);
                this.photoInfo = this.GetPhotoInfo(binaryReader, this.directoryDataArray);
            }
        }

        /// <summary>
        /// X3Fファイルから画像を取得する。
        /// </summary>
        /// <param name="type">取得する画像の種類。</param>
        /// <returns>画像。</returns>
        public Image GetImage(ImageType type)
        {
            Image img;
            using (BinaryReader binaryReader = new BinaryReader(File.OpenRead(path)))
            {
                byte[] imageData = new byte[this.directoryDataArray[(int)type].size - 28];
                imageData = ReadBytes(binaryReader, this.directoryDataArray[(int)type].offset + 28, this.directoryDataArray[(int)type].size - 28);
                img = (Image)(new ImageConverter().ConvertFrom(imageData)); 
            }
            return img;
        }

        /// <summary>
        /// X3Fファイルから撮影情報を取得する。
        /// </summary>
        /// <param name="binaryReader">取得するX3FファイルのBinaryReader。</param>
        /// <param name="directoryDataArray">X3Fファイルのディレクトリ情報を記録した配列。</param>
        /// <returns>撮影情報。</returns>
        private PhotoInfo GetPhotoInfo(BinaryReader binaryReader, DirectoryData[] directoryDataArray)
        {
            Int32 entries_num = BitConverter.ToInt32(this.ReadBytes(binaryReader, directoryDataArray[2].offset + 8, 4), 0);
            Int32 length = BitConverter.ToInt32(this.ReadBytes(binaryReader, directoryDataArray[2].offset + 20, 4), 0);//文字長。2バイト文字でカウントされている。
            Int32 offset = directoryDataArray[2].offset + 24 + entries_num * 8;
            string photoInfoString = Encoding.Unicode.GetString(this.ReadBytes(binaryReader, offset, length * 2));

            PhotoInfo info = new PhotoInfo();
            string[] tmp = photoInfoString.Split('\0');

            info.AEMODE = tmp[1];
            info.AFMODE = tmp[3];
            info.AP_DESC = tmp[5];
            info.APERTURE = tmp[7];
            info.BRACKET = tmp[9];
            info.BURST = tmp[11];
            info.CAMMANUF = tmp[13];
            info.CAMMODEL = tmp[15];
            info.CAMNAME = tmp[17];
            info.CAMSERIAL = tmp[19];
            info.DRIVE = tmp[21];
            info.EXPCOMP = tmp[23];
            info.EXPNET = tmp[25];
            info.EXPTIME = tmp[27];
            info.FIRMVERS = tmp[29];
            info.FLASH = tmp[31];
            info.FLENGTH = tmp[33];
            info.FLEQ35MM = tmp[35];
            info.FOCUS = tmp[37];
            info.IMAGERBOARDID = tmp[39];
            info.IMAGERTEMP = tmp[41];
            info.ISO = tmp[43];
            info.LENSARANGE = tmp[45];
            info.LENSFRANGE = tmp[47];
            info.LENSMODEL = tmp[49];
            info.PMODE = tmp[51];
            info.RESOLUTION = tmp[53];
            info.SENSORID = tmp[55];
            info.SH_DESC = tmp[57];
            info.SHUTTER = tmp[59];
            info.TIME = tmp[61];
            info.WB_DESC = tmp[63];

            return info;
        }

        /// <summary>
        /// X3Fファイルからファイル内のディレクトリ情報を取得する。
        /// </summary>
        /// <param name="binaryReader">取得するX3FファイルのBinaryReader。</param>
        /// <returns>ディレクトリ情報を格納した配列。</returns>
        private DirectoryData[] GetDirectoryIndex(BinaryReader binaryReader)
        {
            DirectoryData[] directoryDataArray;
            Int32 offset = BitConverter.ToInt32(ReadBytes(binaryReader, Convert.ToInt32(binaryReader.BaseStream.Length - 4), 4), 0);
            Int32 entries_num = BitConverter.ToInt32(ReadBytes(binaryReader, offset + 8, 4), 0);
            directoryDataArray = new DirectoryData[entries_num];
            for (int i = 0; i < entries_num; i++)
            {
                Int32 p = offset + 12 + i * 4 * 3;
                directoryDataArray[i] = new DirectoryData();
                directoryDataArray[i].offset = BitConverter.ToInt32(ReadBytes(binaryReader, p, 4), 0);
                directoryDataArray[i].size = BitConverter.ToInt32(ReadBytes(binaryReader, p + 4, 4), 0);
                directoryDataArray[i].tag = Convert.ToBase64String(ReadBytes(binaryReader, p, 4));
            }
            return directoryDataArray;
        }

        /// <summary>
        /// BinaryReaderを指定のオフセットへ移動させ、指定のサイズ(Byte)だけ読み取る。
        /// </summary>
        /// <param name="binaryReader">読み込むバイナリのソース。</param>
        /// <param name="offset">バイナリリーダーのオフセット。</param>
        /// <param name="size">読み込むサイズ。</param>
        /// <returns></returns>
        private Byte[] ReadBytes(BinaryReader binaryReader, Int32 offset, Int32 size)
        {
            binaryReader.BaseStream.Position = offset;
            return binaryReader.ReadBytes(size);
        }

        public override string ToString()
        {
            return this.Path;
        }
    }
}

参考文献

X3F SIGMA Raw format documentation project