Photoshop 7 で作成したBitmapを読み込むには?


バレル  2004-09-29 09:16:32  No: 11199

Photoshop 7 でWindows形式で保存したBitmapをTImageなどに読み込もうとすると、「ストリームからの読み込みエラー」が発生して読み込めないことがあります。
読み込めないBitmapとペイントで保存した読み込めるBitmapをバイナリエディタで開いて比べてみると、読み込めないBitmapのほうが情報データが2byte多くなっていました。
なぜ、読み込めないことがあるのでしょうか?
どなたか、ご教授いただけないでしょうか?


ウォレス  URL  2004-09-29 20:11:55  No: 11200

https://www.petitmonte.com/bbs/answers?question_id=1556
ここに類似のトピックが。

もしお使いのフォトショのバージョンが7.01以降であれば同じ現象ですね。

そういえば、「後で試す」って書いて試してないなあ・・・(汗

恐らくヘッダがVCLでは許容できない形式なのかと。


sadoyama  URL  2004-09-30 00:40:23  No: 11201

photoshop を持っていないので私には解りませんが、
新種のフォーマットか、Delphi TBitmap が対応していないフォーマットを使っていると思われます。
見本をいただければ解析して原因が解ると思います。
解決法についてもたぶん提案できると思います。
もしよろしければ、上記アドレスへメールで送っていただけませんか。


スタテツ  2004-09-30 02:31:02  No: 11202

他力本願ですが…
私もこれに遭遇しましたが、中村さんのNkDIBは見事にサポートされています。これを使うのが一番手っ取り早い気がします。


sadoyama  URL  2004-10-01 05:12:28  No: 11203

パレルさんより、サンプルを提供していただきました。
私の結論は、Photoshopの明白なバグで、近々修正パッチが出るものと思われます。
ステタツさんの指摘どおりであれば、当面はこれで凌いでおけばよいかと。

詳しい見解と、私が使用したツールは、ソースを含めて上記アドレスよりダウンロードできるようにしましたので、関心のある方はご利用ください。
ただし、サイトの容量(10MB)が逼迫をしておりますので、2004.10月いっぱいで削除させていただきます。
100MBに増やせたら、復活させたいと思います。


jok  2004-10-01 12:15:03  No: 11204

> 私の結論は、Photoshopの明白なバグで、近々修正パッチが出るものと思われます。

そうとは思いません。Delphi の TBitmap が TBitmapFileHeader の bfOffBits
を読み込んでいないせいだと思います。TBitmapInfoHeader のあとに隙間なくデータ
が続くと仮定しているのが誤りです。多くのグラフィックソフトは bfOffBits を
ちゃんと解釈してカラーデータの始まる位置を取得しているので適正に読み込めます。


jok  2004-10-01 12:22:54  No: 11205

[Delphi-ML:61805] TBitmap のバグ
http://www2.big.or.jp/~osamu/Delphi/delphi-browse.cgi?index=061805

中村さんのTNkDIBは上記のバグを修正しています。


性善説  2004-10-02 00:10:45  No: 11206

ストリームエラーを起こすBMPファイルは、sadoyamaさんのPhotoshopbmp.txtに書いてある通り、BitmapInfo^.bmiHeader.biSizeImageの値が不正ですね。
128x128のBitmapの場合なら、biSizeImageの値は
【正】49,152バイト
【誤】49,154バイト

VCLのソースを見てみると、この biSizeImage値が0の時は正しい値を計算してセットしますが、
0でない時は計算しないで、その値をそのまま使用しています。
つまり、VCLは BMPファイルのヘッダ値が「常に正しい」ことを信じていることになりますね。
したがって、期待に反して、正しい値より大きい「不正な biSizeImage値」がセットされているBMPファイルの場合にはストリームエラーが出るわけです。

以下は Graphics.pas の 5651〜5656行です。
VCLソースを持っている人は、Graphics.pasファイルをプロジェクトのフォルダにコピーしてから、その 5652行をコメントアウトして強制的に ImageSizeの値を修正すれば、ストリームエラーは出なくなります。

5651:      // biSizeImage can be zero. If zero, compute the size.
5652://*** if biSizeImage = 0 then            // top-down DIBs have negative height
5653:        biSizeImage := BytesPerScanLine(biWidth, biBitCount, 32) * Abs(biHeight);
5654:
5655:      if biSizeImage < ImageSize then ImageSize := biSizeImage;
5656:    end;


joy  2004-10-02 00:52:52  No: 11207

>BitmapInfo^.bmiHeader.biSizeImageの値が不正ですね。

なるほど、TBitmap が bfOffBits を見ないことではなかったんですね。
わたしの回答は撤回します。


jok  2004-10-02 00:59:42  No: 11208

すみません、上のハンドルを間違えました。


sadoyama  URL  2004-10-02 08:58:31  No: 11209

性善説さん、解明ありがとうございました。何となく理解できました。
私もソースを追っていましたが届きませんでした。

MSOfficeのPhotoEdit.exe、岡本さんのtokikaze.exeを試してみましたが、
どちらでも問題のファイルをロードできました。
BitmapFileHeader.bfOffBits で画像データのアドレスが正しく設定されていれば、この程度のことは寛容に対処すべき、ということでしょうか。
私としては、DelphiのTBitmapを責める気にはなれませんが。

しかしTBitmapにもJokさんが指摘していた弱点があるのは事実です。
DelphiにはDelphiの主張があるようですが。
私が勝手に「Memo Field」と読んでいるデータを含んだビットマップに遭遇したことがないのであいまいにしてきましたが、これを機会にNkDIBを研究しようかと思っています。


jok  2004-10-02 09:12:52  No: 11210

sadoyamさん、失礼しました。性善説さん、ありがとうございました。

性善説さんの説明を検証するために、TBitmap に読み込むまえに加工してみました。

procedure TForm1.Button1Click(Sender: TObject);
var
  bmp:TBitmap;
  stm:TMemoryStream;
  biSizeImage:DWORD;
begin
  bmp := TBitmap.Create;
  stm := TMemoryStream.Create;
  try
    stm.LoadFromFile('C:\128x128.bmp');
    stm.Position := 34;
    biSizeImage := 0;
    stm.Write(biSizeImage,4);
    stm.Position := 0;
    bmp.LoadFromStream(stm);
    Canvas.Draw(10,10,bmp);
  finally
    bmp.Free;
    stm.Free;
  end;
end;

このように TBitmapInfoHeader.biSizeImage にゼロまたは正しい 49152 を予め
書き込んでおくと正常に読み込めますね。
同様にして TBitmapFileHeader の bfOffBits を読み込まないバグも、データの
方を加工することで回避出来そうです。いろいろな場合を想定して位置を決定しな
ければならないのでコードは煩雑になりそうです。しかし、このような対症療法より、
TNkDIB のようなクラスを使うのが最善ですね。


バレル  2004-10-07 13:24:49  No: 11211

長い間、レスが書けず申し訳ありませんでした。
みなさんのお陰で対応策や原因が分かってきましたが、
このエラーが発生する条件は、データサイズが2byte大きいかどうかということだけではないようです。
こちらでいろいろ試した結果、ビットマップの縦のサイズが64の倍数のときに読み込めないようです。


sadoyama  URL  2004-10-08 09:35:14  No: 11212

ようやくTBitmap のソースの該当部分を見終わりました。
  しかし、EReadError 例外がなぜ起きるのか解りませんでした。

  パレルさんより追加のサンプルを得ました。
  試した結果は、次のとおりです。
  X:EReadError; 0: nonError;

  red32x32.bmp      0
  red63x63.bmp      0
  red64x64.bmp      X
  red65x65.bmp      0
  red127x127.bmp    0
  red127x128.bmp    X
  red128x127.bmp    0
  red128x128.bmp    X
  red129x129.bmp    0
  red256x256.bmp    X
  
  現象的には、次のことが確認できました。
1.BitmapInfoHeader.biSizeImage の値が不正で、
    かつ、Height が64の倍数の場合のみ例外が発生する。
2. 性善説さんの指摘のようにGraphic.pasを修正し、
    biSizeImageが常に正しくセットされるようにすると例外は発生しない。
3. BitmapInfoHeader.biSizeImage の値が不正のままであっても、
    ファイルサイズがただしければ、エラーとならない。

3.については、パレルさんのサンプルから、ファイル末尾2バイトを削除して確認したことです。
  今回の事例に限り有効です。
  つまり、(1) BitmapFileHeader.bfOffBits の値が正しい。
          (2) 実際のイメージデータも bfOffBits から始まっている。
          (3) 不正部分は、ファイル末尾に存在している。
  という条件です。

  しかし、biSizeImage が不正であろうと、ファイルサイズが不正であろうと、
なぜ EReadError 例外が発生するの理由が解りません。
  どなたか教えていただけないでしょうか。
  尚、追加のサンプルも先の私のトピックのアドレスでダウンロードできるようにしました。私が作成し直した2バイト削除ファイルも同様です。

  10.01のトピックで性善説さんが示された
5652://*** if biSizeImage = 0 then            // top-down DIBs have negative height
を基準(Delphi7 では 5912 にあたります)にソースを示してみます。

----------------------------------------------------------------------

  Delphi 7 では、56行くらいあとの

  Stream.ReadBuffer(BitsMem^, ImageSize);  で例外が発生します。// <A>

  この手続きは、ImageSize に設定された値が0の場合か、
  実際に読み込めた=存在していた読み込み対象のデータサイズと、
  読み込みサイズを指定した ImageSize とが異なる場合に例外を起こします。
  エラーとなるビットマップの場合に実際に読み込めたバイト数を調べたところ、ゼロでした。
  すなわち、読み込みに失敗していたわけです。
  この手続きは、最終的には ReadFile() API で読み込みを実行しています。
  では、ReadFile() API はどういう場合に失敗するのでしょう。

(1) 読込先バッファへのポインタ BitsMem が nil の場合。
  今回は、これはあり得ません。基準より 42行あとに
  if (BMHandle = 0) or (BitsMem = nil) then      // <B>
    if GetLastError = 0 then InvalidBitmap else RaiseLastOSError;

  があり、そうであればここで例外が発生し <A> にまで到達できません。

(2) BitsMem が示す先にメモリが確保されていない
  メモリの確保は、<B>より1行前の
  BMHandle := CreateDIBSection(DC, BitmapInfo^, DIB_RGB_COLORS, BitsMem, 0, 0);
で CreateDIBSection() API が行っているはずですが、どのようにしてくれているのか解りません。

(3) 確保されたメモリサイズが少なすぎる
  メモリの確保が正しいイメージサイズで行われていたとしても、
  私の実験では、確保サイズより2バイトくらい多く読み込みを指定しても実行できてしまいます。
  すなわち、書き込んではいけない領域にも連続して書き込んでしまう。
  Windows も Delphi も管理してくれない。

(4) 読み込み対象のデータが存在しない
  これも今回はあり得ません。


推定有罪?  2004-10-08 23:55:42  No: 11213

>なぜ EReadError 例外が発生するのか理由が解りません。
その理由(例外発生の原因)として、
>(3) 確保されたメモリサイズが少なすぎる
に1票。

それは、Graphics.pasの
  Stream.ReadBuffer(BitsMem^, ImageSize);
の行の前に
  ImageSize := ImageSize + 1;
を追加すればすべてのBitmapが EReadError例外を出すことから分かる。
(ただし、エラーにならない妙なBitmapも存在するような…)

以下は推定で断定にあらず。

  BMHandle := CreateDIBSection(DC, BitmapInfo^, DIB_RGB_COLORS, BitsMem, 0, 0);
この関数の中で BitsMemのメモリ領域を確保する際には、BitmapInfo.biSizeImage値は使われずに、
biWidth、biHeight値などからあらためて計算し直している。
そして、Bitmapの縦のサイズが64の倍数ではない場合には、その計算された必要サイズにさらに2Byte追加している。
なぜ縦のサイズが64の倍数ではない場合に 2Byte余計にメモリを確保するのか? これはおそらく行儀の悪いBitmap作成ソフトの存在を考慮しているためと思われる。

ところが、Photoshop7は、これを「どんなサイズのBitmapを読み込む時にもメモリは 2Byte余計に確保される」と勘違いして、常に末尾に2Byte加えたBitmapファイルを作成している。
Photoshop7のこの行為は無罪?それとも有罪?


sadoyama  URL  2004-10-09 05:50:14  No: 11214

推定有罪さん write
> Stream.ReadBuffer(BitsMem^, ImageSize); の行の前に
>   ImageSize := ImageSize + 1;
> を追加すればすべてのBitmapが EReadError例外を出すことから分かる。
試してみると確かにそのとおりです。

私も同じような原因しか考えられなかったのですが、
TFileStream.Read を私なりにテストしてみると、「そう厳密ではない」という結果が出ます。そこで解らなくなりました。
確保したバッファのサイズが読み込み指定サイズより1バイト少ないときはまずエラーにならない。
4バイト少ないと必ずエラーとなる。
2,3バイト少ない場合はケースバイケース。
エラーといっても、「無効なポインタ操作」というメッセージで、
TFileStream.Read が返す読み込めたサイズには問題がない。
しかし、推定有罪さんの指摘どおりにすると、厳密にエラーとなる。

TStream は今回が初めての経験なので、初歩的な考え違いが私にあるような気がします。
私がテストしたコードを載せますので、コメントしてくださる方があれば幸いです。
環境  Delphi7, Windows2000 SP4

-----------------------------------------------------------------

function  GetFileSize(PathFName: string): integer;
procedure OnlyNumber(var Key: Char; flgMark, flgReal: Boolean);

implementation

{$R *.dfm}

function GetFileSize(PathFName: string): integer;
var
  SearchRec: TSearchRec;
  Res: Integer;
begin
  Res := Findfirst(PathFName, faAnyFile, SearchRec);
  if Res = 0 then
    Result := SearchRec.Size
  else
    Result := 0;
end;

procedure OnlyNumber(var Key: Char;
                     flgMark, flgReal: Boolean);
begin
  //数字が見つからなかったら
  if (Pos(Key, '0123456789')=0) and (Key <> #8)
  and (Key <> #13) and (Key <> '-') and (Key <> '.') then
  begin
    Key := #0; //無視する
  end else
  begin
    if (not flgMark) and (Key = '-') then
      Key := #0;  //無視する
    if (not flgReal) and (Key = '.') then
      Key := #0;  //無視する
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  if not OpenDialog1.Execute then  Exit;
  Memo1.Clear;
  Memo1.Lines.Add('FileName: ' +
                   ExtractFileName(OpenDialog1.FileName));
  Edit1.Text := '0';
  Button2.Enabled := true;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  FStream: TFileStream;
  FileSize, ReadSize, DecCount, BuffSize: Integer;
  Buff: Pointer;
begin
  Memo1.Clear;
  Memo1.Lines.Add('FileName: ' +
                   ExtractFileName(OpenDialog1.FileName));

  FStream := TFileStream.Create(OpenDialog1.FileName,
                                fmOpenRead);
  try
    FileSize := GetFileSize(OpenDialog1.FileName);
    Memo1.Lines.Add('FileSize=ReadSize: ' + IntToStr(FileSize));

    DecCount := StrToInt(Edit1.Text);
    BuffSize := FileSize - DecCount;

    GetMem(Buff, BuffSize);
    Memo1.Lines.Add('BuffSize: ' + IntToStr(BuffSize));
    try
      ReadSize := FStream.Read(Buff^, FileSize);
      Memo1.Lines.Add('ReadedSize: ' + IntToStr(ReadSize));
    finally
      FreeMem(Buff, BuffSize);  // ポインタ操作エラー発生ケ所
    end;
  finally
    FStream.Free;
  end;

  if ReadSize = FileSize then
    Memo1.Lines.Add(#13#10 + 'エラーなし')
  else  Memo1.Lines.Add(#13#10 + 'エラー!!');
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Button2.Enabled := false;
end;

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
  OnlyNumber(Key, false, false);
end;

procedure TForm1.Edit1Click(Sender: TObject);
begin
  Edit1.SelStart := 0;
  Edit1.SelLength := Length(Edit1.Text);
end;

procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word;
                              Shift: TShiftState);
begin
  if Key = VK_Return then  Button2Click(Sender);
end;

----------------------------------------------------------------


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加