2010年12月25日

GIFファイルの書き込み 第2部



「GIFファイルの書き込み 第2部」では、GIFファイルに各ブロックを書き込む手順について解説します。

  1. StreamOut関数で作成した画像情報から、GIFヘッダーを書き込みます。
  2. アニメーションGIF(複数画像)の場合は、アプリケーションブロックを書き込みます。
  3. コメント文がある場合、コメントブロックを書き込みます。
  4. プレーンテキスト文がある場合、プレーンテキストブロックを書き込みます。
  5. 透過画像、表示時間、インターレース、ディスポサル方法の指定がある場合、グラフィックブロックを書き込みます。
  6. StreamOut関数で作成したグローバル(ローカル)カラーテーブル32ビットピクセルデータから、イメージブロックを書き込みます。
  7. 5.と6.の作業を画像枚数分だけ繰り返します。





    StreamOut関数で作成した画像情報から、GIFファイルに各ブロックを書き込みます。

    //現在の値から各ブロックを作成して、ファイルに書き込みます。
    BOOL
    CGIFFile::WriteLocalBlock()
    {
    //GIFヘッダーの書き込み
    if (!WriteHeader()) return FALSE;
    //アプリケーションブロックの書き込み
    if ((m_bNetScapeExt)&&(!WriteApplicationBlock())) return FALSE;
    //コメントブロックの書き込み
    if ((!m_strComment.IsEmpty())&&(!WriteCommentBlock())) return FALSE;
    //プレーンテキストブロックの書き込み
    if ((!m_strPlainText.IsEmpty())&&(!WritePlainTextBlock())) return FALSE;

    for(int i=0;i<m_listLocal.GetCount();i++){

    //CGIFLocalクラスへのポインタを取得します。
    CGIFLocal* pLocal=GetLocal(i);
    //メンバー変数にCGIFLocalクラスの各メンバー変数を設定します。
    m_disposalMethod=pLocal->m_disposalMethod; //ディスポサル方法
    m_bTransparent=(pLocal->m_indexTransparent!=-1);//透過色フラグ
    m_indexTransparent=pLocal->m_indexTransparent; //透過色番号
    m_delay=pLocal->m_delay; //表示時間
    m_bGraphicBlock=((m_disposalMethod)||(m_bTransparent)||(m_delay));
    //グラフィックブロックの書き込み
    if ((m_bGraphicBlock)&&(!WriteGraphicBlock())) return FALSE;

    m_bInterlace=pLocal->m_bInterlace;
    m_pBitsWrite32=pLocal->m_pBitsWrite32;

    //ローカルカラーマップを作成します。
    if (pLocal->m_pRgbq){
    m_bLct=TRUE;
    m_nLctCount=pLocal->m_cEntries;
    m_prgbqLct=pLocal->m_pRgbq;
    //色数が奇数ならインクリメントします。
    }
    else{
    m_bLct=FALSE;
    m_nLctCount=0;
    m_prgbqLct=0;
    }
    BOOL bResult=WriteImageDescriptor();
    m_pBitsWrite32=0;
    if (!bResult) return FALSE;
    }
    return TRUE;
    }


    StreamOut関数で作成したグローバルカラーテーブルヘッダー情報を、GIFファイルに書き込みます。

    //GIFヘッダーの書き込み
    BOOL CGIFFile::WriteHeader()
    {
    //バージョン番号を設定します。("87a"若しくは"89a")
    //スタティック変数に直書きするので、
    //マルチスレッドで書き込むときはアクセスが重ならないようにします。

    char Version[3]={'8','7','a'};
    if ((!m_strComment.IsEmpty())||
    (!m_strPlainText.IsEmpty())||
    (m_bGraphicBlock)||
    (m_bNetScapeExt))
    {
    Version[1]='9';
    }
    ::EGifSetGifVersion(Version);

    //グローバルカラーテーブルがあれば
    //ColorMapObject構造体を作成します。

    GifColorType colorMap[256]={0};
    ColorMapObject* pColorMap=0;
    int indexBackground=0;
    if (m_bGct){
    //背景色番号を取得します。
    if (m_bBackground){
    ASSERT((m_rgbBackground&0xFF000000)==0);
    //RGBからBGRに変換します。
    DWORD bgr=((m_rgbBackground&0xFF)<<16)|(m_rgbBackground&0xFF00)|((m_rgbBackground>>16)&0xFF);
    //グローバルカラーテーブル内で一致した色の番号を取得します。
    indexBackground=_GetFixedColorIndex(0,m_nGctCount,m_prgbqGct,bgr);
    if (indexBackground==-1) indexBackground=0;
    }
    for(UINT i=0;i<m_nGctCount;i++){
    colorMap[i].Blue =m_prgbqGct[i].rgbBlue;
    colorMap[i].Green=m_prgbqGct[i].rgbGreen;
    colorMap[i].Red =m_prgbqGct[i].rgbRed;
    }
    //色数を2の階乗にします。
    m_nGctCount=(1<<::BitSize(m_nGctCount));
    //ColorMapObject構造体を作成します。
    pColorMap=::MakeMapObject(m_nGctCount,colorMap);
    }
    //GIFヘッダーの書き込み
    int ColorRes=(pColorMap)?pColorMap->BitsPerPixel:0;
    int nResult=::EGifPutScreenDesc(m_pGifInfo,Width(),Height(),ColorRes,indexBackground,pColorMap);
    //ColorMapObject構造体があれば削除します。
    if (pColorMap) ::FreeMapObject(pColorMap);
    if (nResult==GIF_ERROR) return FALSE;
    return TRUE;
    }



    StreamOut関数で作成したグローバル(ローカル)カラーテーブルと画像データから、GIFファイルにイメージブロックを書き込みます。

    //イメージブロックの書き込み
    BOOL CGIFFile::WriteImageDescriptor()
    {
    ASSERT(m_pGifInfo);

    //ビットマップビット幅の算出
    UINT widthBytes32=WIDTHBYTES(32*m_LSWidth);
    //ピクセルデータのバイト数を算出します。
    UINT sizeImage32=widthBytes32*m_LSHeight;
    if (!AfxIsValidAddress(m_pBitsWrite32,sizeImage32,0)) return FALSE;

    //出来る限り画像の余白は切り取ります。
    UINT xmin=m_LSWidth; //x軸の最小値
    UINT ymin=m_LSHeight; //y軸の最小値
    UINT xmax=0; //x軸の最大値
    UINT ymax=0; //y軸の最大値
    for(UINT i=0;i<m_LSHeight;i++){
    DWORD* src=(DWORD*)(m_pBitsWrite32+widthBytes32*(m_LSHeight-i-1));
    for(UINT j=0;j<m_LSWidth;j++){
    //ピクセルの色が透過色(0xFF000000)ではない場合、
    //座標位置から、各軸の最大値/最小値を求めます。

    if (((*src++)&0xFF000000)==0) {
    if (xmin>j) xmin=j;
    if (xmax<j) xmax=j;
    if (ymax<i) ymax=i;
    if (ymin>i) ymin=i;
    }
    }
    }
    m_left=(xmin<m_LSWidth)?xmin:0;
    m_top=(ymin<m_LSHeight)?ymin:0;
    m_width=xmax-m_left+1;
    m_height=ymax-m_top+1;

    //ローカルカラーテーブルがある場合は、ColorMapObject構造体を作成します。
    GifColorType colorMap[256]={0};
    ColorMapObject* pColorMap=0;
    if (m_bLct){
    for(UINT i=0;i<m_nLctCount;i++){
    colorMap[i].Blue =m_prgbqLct[i].rgbBlue;
    colorMap[i].Green=m_prgbqLct[i].rgbGreen;
    colorMap[i].Red =m_prgbqLct[i].rgbRed;
    }
    //色数を2の階乗にします。
    m_nLctCount=(1<<::BitSize(m_nLctCount));
    pColorMap=::MakeMapObject(m_nLctCount,colorMap);
    }

    //イメージブロックを書き込みます。
    int nResult=::EGifPutImageDesc(m_pGifInfo,m_left,m_top,m_width,m_height,m_bInterlace,pColorMap);
    //ローカルカラーテーブルのColorMapObject構造体があれば削除します。
    if (pColorMap) FreeMapObject(pColorMap);
    if (nResult==GIF_ERROR) return FALSE;

    //カラーテーブルと色数の設定
    //ローカルカラーテーブルがある場合は、ローカルカーテーブルとその色数を選択し、
    //ない場合は、グローバルカラーテーブルとその色数を選択します。

    RGBQUAD* pRgbq=(m_bLct)? m_prgbqLct:m_prgbqGct;
    UINT nColorCount=(m_bLct)? m_nLctCount:m_nGctCount;

    //グローバルカラーテーブルもローカルカラーテーブルもなければ、
    //FALSEを返して終了します。

    if (!pRgbq) return FALSE;

    ASSERT(nColorCount%2==0); //色数が奇数ならエラー
    ASSERT(nColorCount<=256); //色数が256以上ならエラー

    //ピクセルデータを引き渡すラインバッファを作成します。
    GifPixelType* pLine=new GifPixelType[m_width];
    //インターレースGIFの場合
    if (m_bInterlace)
    {
    //4段階で書き込む行を変更します。
    for (int pass=0;pass<4;pass++){
    for (UINT i=g_InterlacedOffset[pass];i<m_height;i+=g_InterlacedJumps[pass]){
    //ピクセルバッファへのポインタを算出します。
    DWORD* src=(DWORD*)((PBYTE)m_pBitsWrite32+widthBytes32*(m_LSHeight-i-1-m_top)+m_left*4);
    PBYTE dst=(PBYTE)pLine;
    for(UINT j=0;j<m_width;j++){
    //ピクセルデータを取り出します。
    DWORD rgbPix=*src++;
    int index=m_indexTransparent;
    //ピクセルの色が透過色ではない場合、
    //カラーテーブルから色番号を探します。

    if ((rgbPix&0xFF000000)==0){
    DWORD* rgbq=(DWORD*)pRgbq;
    for(UINT k=0;k<nColorCount;k++){
    if (rgbPix==*rgbq++){
    index=k;
    break;
    }
    }
    }
    ASSERT(index!=-1);
    //ラインバッファに格納します。
    *dst++=index;
    }
    //ラインバッファをファイルに書き出します。
    if (::EGifPutLine(m_pGifInfo,pLine,m_width)==GIF_ERROR){
    delete[]pLine;
    return FALSE;
    }
    }
    }
    }
    //通常の画像の場合
    else{
    for (UINT i=0;i<m_height;i++){
    //ピクセルバッファへのポインタを算出します。
    DWORD* src=(DWORD*)((PBYTE)m_pBitsWrite32+widthBytes32*(m_LSHeight-i-1-m_top)+m_left*4);
    PBYTE dst=(PBYTE)pLine;
    for(UINT j=0;j<m_width;j++){
    //ピクセルデータを取り出します。
    DWORD rgbPix=*src++;
    int index=m_indexTransparent;
    //ピクセルの色が透過色ではない場合、
    //カラーテーブルから色番号を探します。

    if ((rgbPix&0xFF000000)==0){
    DWORD* rgbq=(DWORD*)pRgbq;
    for(UINT k=0;k<nColorCount;k++){
    if (rgbPix==*rgbq++){
    index=k;
    break;
    }
    }
    }
    ASSERT(index!=-1);
    //ラインバッファに格納します。
    *dst++=index;
    }
    //ラインバッファをファイルに書き出します。
    if (::EGifPutLine(m_pGifInfo,pLine,m_width)==GIF_ERROR){
    delete[]pLine;
    return FALSE;
    }
    }
    }
    //ラインバッファを削除します。
    delete[]pLine;

    return TRUE;
    }


    拡張ブロックを書き込む関数です。

    //アプリケーションブロックの書き込み
    BOOL CGIFFile::WriteApplicationBlock()
    {
    ASSERT(m_pGifInfo);
    if (!m_bNetScapeExt) return FALSE;

    //"NETSCAPE2.0"文字列を書き込みます。
    BYTE NetScapeExt[APPLICATION_SIZE]={'N','E','T','S','C','A','P','E','2','.','0'};
    if (::EGifPutExtensionFirst(m_pGifInfo,APPLICATIONEXTENSION,APPLICATION_SIZE,&NetScapeExt)
    ==GIF_ERROR) return FALSE;

    BYTE subBlock[5]={0};

    //ループ回数を書き込みます。
    subBlock[0]=LOOP_COUNT_CODE;
    *((WORD*)&subBlock[1])=(WORD)m_nLoopCounts;
    if (::EGifPutExtensionNext(m_pGifInfo,LOOP_COUNT_CODE,3,subBlock)
    ==GIF_ERROR) return FALSE;

    //バッファサイズがあれば書き込みます。
    if (m_nBufferBytes){
    subBlock[0]=BUFFER_BYTES_CODE;
    *((DWORD*)&subBlock[1])=m_nBufferBytes;
    if (::EGifPutExtensionNext(m_pGifInfo,BUFFER_BYTES_CODE,5,subBlock)
    ==GIF_ERROR) return FALSE;
    }

    //ブロックターミネータを書き込みます。
    if (::EGifPutExtensionLast(m_pGifInfo,0,0,0)==GIF_ERROR) return FALSE;
    return TRUE;
    }

    //コメントブロックの書き込み
    BOOL CGIFFile::WriteCommentBlock()
    {
    ASSERT(m_pGifInfo);
    if (m_strComment.IsEmpty()) return FALSE;
    //コメント文を書き込みます。
    if (::EGifPutComment(m_pGifInfo,m_strComment.GetBuffer())==GIF_ERROR) return FALSE;
    return TRUE;
    }

    //プレーンテキストブロックの書き込み
    BOOL CGIFFile::WritePlainTextBlock()
    {
    ASSERT(m_pGifInfo);
    if (m_strPlainText.IsEmpty()) return FALSE;

    //各パラメータの書き込み
    BYTE PlainText[PLAIN_TEXT_SIZE]={0};
    *((WORD*)(&PlainText[0]))=(WORD)m_nTextGridLeft;
    *((WORD*)(&PlainText[2]))=(WORD)m_nTextGridTop;
    *((WORD*)(&PlainText[4]))=(WORD)m_nTextGridWidth;
    *((WORD*)(&PlainText[6]))=(WORD)m_nTextGridHeight;
    PlainText[8] =(BYTE)m_nCharCellWidth;
    PlainText[9] =(BYTE)m_nCharCellHeight;
    PlainText[10]=(BYTE)m_nTextForegroundColorIndex;
    PlainText[11]=(BYTE)m_nTextBackgroundColorIndex;
    if (::EGifPutExtensionFirst(m_pGifInfo,PLAINTEXTEXTENSION,PLAIN_TEXT_SIZE,&PlainText)
    ==GIF_ERROR) return FALSE;

    //プレーンテキストの書き込み
    if (::EGifPutExtensionLast(m_pGifInfo,0,m_strPlainText.GetLength(),m_strPlainText.GetBuffer())
    ==GIF_ERROR) return FALSE;

    return TRUE;
    }

    //グラフィックブロックの書き込み
    BOOL CGIFFile::WriteGraphicBlock()
    {
    ASSERT(m_pGifInfo);
    if (!m_bGraphicBlock) return FALSE;

    //パックフィールドを作成します。
    BYTE GraphicControl[GRAPHIC_CONTROL_SIZE]={0};
    BYTE PackedField=(m_disposalMethod&7)<<2;
    if (m_bUserInputMethod) PackedField|=2;
    if (m_bTransparent) PackedField|=1;
    GraphicControl[0] =PackedField;
    //遅延時間の設定
    *((WORD*)(&GraphicControl[1]))=(WORD)m_delay;
    //透過色番号の設定
    GraphicControl[3] =(m_bTransparent)?
    (BYTE)m_indexTransparent:0;
    //グラフィックブロックを書き込みます。
    if (::EGifPutExtension(m_pGifInfo,GRAPHICCONTROLEXTENSION,GRAPHIC_CONTROL_SIZE,&GraphicControl)
    ==GIF_ERROR) return FALSE;
    return TRUE;
    }


    <<GIFファイルの書き込み 第1部ページの先頭GIFファイルの書き込み 第3部>>




posted by ひろし at 19:13| Comment(0) | GIFファイルフォーマット | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
×

この広告は180日以上新しい記事の投稿がないブログに表示されております。