如何顯示一個很大的bmp圖檔 |
尚未結案
|
sam_000
一般會員 發表:27 回覆:47 積分:14 註冊:2003-09-15 發送簡訊給我 |
|
hagar
版主 發表:143 回覆:4056 積分:4445 註冊:2002-04-14 發送簡訊給我 |
|
wameng
版主 發表:31 回覆:1336 積分:1188 註冊:2004-09-16 發送簡訊給我 |
補充: 事實上,會出現記憶體不足的ERROR 。
Sometime 並不是真的不足。主要是GDI 資源不足的問題。
尤其主要發生在Win9x/Me 的作業系統下。
若換為JPEG 格式,就可能不會了。 我記得Delphi 有一本書。(日本人寫的)
內容有提到原因。 我用Google 找到下列源碼。您可以參考他的作法。
是利用只載入顯示有看到的部分。
unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, ExtDlgs; type TForm1 = class(TForm) PaintBox1: TPaintBox; Panel1: TPanel; Button1: TButton; Label1: TLabel; procedure PaintBox1Paint(Sender: TObject); procedure Button1Click(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure FormResize(Sender: TObject); private { Private declarations } FBitmap: TBitmap; FFileName: string; public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} Function MyGetMem( size: DWORD ): pointer; Begin result := Pointer(GlobalAlloc(GPTR, size)); End; Procedure MyFreeMem( p: pointer ); Begin If p = Nil Then Exit; GlobalFree(THandle(p)); End; { This code will fill a bitmap by resampling an image coming from a big bitmap on disk. FileName.- Name of the uncompressed bitmap to read DestBitmap.- Target bitmap where the bitmap on disk will be resampled. BufferSize.- The size of a memory buffer used for reading scanlines from the physical bitmap on disk. This value will decide how many scanlines can be read from disk at the same time, with always a minimum value of 2 scanlines. Will return false on error. } Function GetDIBInBands( Const FileName: String; DestBitmap: TBitmap; BufferSize: integer; Out TotalBitmapWidth, TotalBitmapHeight: Integer): Boolean; Var FileSize: integer; // calculated file size ImageSize: integer; // calculated image size dest_MaxScans: integer; // number of scanline from source bitmap dsty_top: Integer; // used to calculate number of passes NumPasses: integer; // number of passed needed dest_Residual: integer; // number of scanlines on last band Stream: TStream; // stream used for opening the bitmap bmf: TBITMAPFILEHEADER; // the bitmap header lpBitmapInfo: PBITMAPINFO; // bitmap info record BitmapHeaderSize: integer; // size of header of bitmap SourceIsTopDown: Boolean; // is reversed bitmap ? SourceBytesPerScanLine: integer; // number of bytes per scanline SourceLastScanLine: Extended; // last scanline processes SourceBandHeight: Extended; // BitmapInfo: PBITMAPINFO; img_start: integer; img_end: integer; img_numscans: integer; OffsetInFile: integer; OldHeight: Integer; bits: Pointer; CurrentTop: Integer; CurrentBottom: Integer; Begin result := False; // open the big bitmap Stream := TFileStream.Create( FileName, fmOpenRead Or fmShareDenyWrite ); // total size of bitmap FileSize := Stream.Size; // read the header Stream.ReadBuffer( bmf, sizeof( TBITMAPFILEHEADER ) ); // calculate header size BitmapHeaderSize := bmf.bfOffBits - sizeof( TBITMAPFILEHEADER ); // calculate size of bitmap bits ImageSize := FileSize - integer( bmf.bfOffBits ); // check for valid bitmap and exit if not If ( ( bmf.bfType <> $4D42 ) Or ( integer( bmf.bfOffBits ) < 1 ) Or ( FileSize < 1 ) Or ( BitmapHeaderSize < 1 ) Or ( ImageSize < 1 ) Or ( FileSize < ( SizeOf( TBITMAPFILEHEADER ) BitmapHeaderSize ImageSize ) ) ) Then Begin Stream.Free; Exit; End; lpBitmapInfo := MyGetMem( BitmapHeaderSize ); Try Stream.ReadBuffer( lpBitmapInfo^, BitmapHeaderSize ); // check for uncompressed bitmap If ( ( lpBitmapInfo^.bmiHeader.biCompression = BI_RLE4 ) Or ( lpBitmapInfo^.bmiHeader.biCompression = BI_RLE8 ) ) Then Begin Exit; End; // bitmap dimensions TotalBitmapWidth := lpBitmapInfo^.bmiHeader.biWidth; TotalBitmapHeight := abs( lpBitmapInfo^.bmiHeader.biHeight ); // is reversed order ? SourceIsTopDown := ( lpBitmapInfo^.bmiHeader.biHeight < 0 ); // calculate number of bytes used per scanline SourceBytesPerScanLine := ((((lpBitmapInfo^.bmiHeader.biWidth * lpBitmapInfo^.bmiHeader.biBitCount) 31) And Not 31) Div 8); // adjust buffer size If BufferSize < Abs( SourceBytesPerScanLine ) Then BufferSize := Abs( SourceBytesPerScanLine ); // calculate number of scanlines for every pass on the destination bitmap dest_MaxScans := round( BufferSize / abs( SourceBytesPerScanLine ) ); dest_MaxScans := round( dest_MaxScans * ( DestBitmap.Height / TotalBitmapHeight ) ); If dest_MaxScans < 2 Then dest_MaxScans := 2; // at least two scan lines // is not big enough ? If dest_MaxScans > TotalBitmapHeight Then dest_MaxScans := TotalBitmapHeight; { count the number of passes needed to fill the destination bitmap } dsty_top := 0; NumPasses := 0; While ( dsty_Top dest_MaxScans ) <= DestBitmap.Height Do Begin inc( NumPasses ); inc( dsty_top, dest_MaxScans ); End; If NumPasses = 0 Then Exit; // calculate scanlines on last pass dest_Residual := DestBitmap.Height Mod dest_MaxScans; // now calculate how many scanlines in source bitmap needed for every band on the destination bitmap SourceBandHeight :=( TotalBitmapHeight * ( 1 - ( dest_Residual / DestBitmap.Height ) ) ) / NumPasses; // initialize first band CurrentTop := 0; CurrentBottom := dest_MaxScans; // a floating point used in order to not loose last scanline precision on source bitmap // because every band on target could be a fraction (not integral) on the source bitmap SourceLastScanLine := 0.0; While CurrentTop < DestBitmap.Height Do begin // scanline start of band in source bitmap img_start := Round( SourceLastScanLine ); SourceLastScanLine := SourceLastScanLine SourceBandHeight; // scanline finish of band in source bitmap img_end := Round( SourceLastScanLine ); If img_end > TotalBitmapHeight - 1 Then img_end := TotalBitmapHeight - 1; img_numscans := img_end - img_start; If img_numscans < 1 Then Break; OldHeight := lpBitmapInfo^.bmiHeader.biHeight; If SourceIsTopDown Then lpBitmapInfo^.bmiHeader.biHeight := -img_numscans else lpBitmapInfo^.bmiHeader.biHeight := img_numscans; // memory used to read only the current band bits := MyGetMem( Abs(SourceBytesPerScanLine) * img_numscans); Try // calculate offset of band on disk OffsetInFile := TotalBitmapHeight - (img_start img_numscans); Stream.Seek( integer( bmf.bfOffBits ) ( OffsetInFile * abs( SourceBytesPerScanLine ) ), soFromBeginning ); Stream.ReadBuffer( bits^, abs( SourceBytesPerScanLine ) * img_numscans ); SetStretchBltMode( DestBitmap.Canvas.Handle, COLORONCOLOR ); // now stretch the band readed to the destination bitmap StretchDIBits( DestBitmap.Canvas.Handle, 0, CurrentTop, DestBitmap.Width, Abs(CurrentBottom - CurrentTop), 0, 0, TotalBitmapWidth, img_numscans, Bits, lpBitmapInfo^, DIB_RGB_COLORS, SRCCOPY ); Finally MyFreeMem( bits ); lpBitmapInfo^.bmiHeader.biHeight := OldHeight; End; CurrentTop := CurrentBottom; CurrentBottom := CurrentTop dest_MaxScans; If CurrentBottom > DestBitmap.Height Then CurrentBottom := DestBitmap.Height; end; Finally Stream.Free; MyFreeMem( lpBitmapInfo ); End; Result:= True; End; procedure TForm1.PaintBox1Paint(Sender: TObject); begin If FBitmap.Empty then exit; PaintBox1.Canvas.Draw(0,0,FBitmap); end; procedure TForm1.Button1Click(Sender: TObject); var bmw, bmh: Integer; begin with TOpenDialog.Create(Nil) do try DefaultExt:= 'BMP'; Filter := 'Bitmaps (*.bmp)|*.bmp'; Title := 'Define bitmap to display'; if not Execute then Exit; FFileName:= FileName; FBitmap.PixelFormat:= pf24Bit; { define the size of the required bitmap } FBitmap.Width:= PaintBox1.ClientWidth; FBitmap.Height:= PaintBox1.ClientHeight; Screen.Cursor:= crHourglass; If not GetDIBInBands( FileName, FBitmap, 100 * 1024, bmw, bmh ) then exit; // 100k of buffer Label1.Caption:= Format( '%d X %d', [bmw, bmh] ); PaintBox1.Invalidate; finally Free; Screen.Cursor:= crDefault; end; end; procedure TForm1.FormCreate(Sender: TObject); begin FBitmap:= TBitmap.Create; end; procedure TForm1.FormDestroy(Sender: TObject); begin FBitmap.free; end; procedure TForm1.FormResize(Sender: TObject); var bmw, bmh: integer; begin If Length(FFileName)=0 then Exit; FBitmap.PixelFormat:= pf24Bit; { define the size of the required bitmap } FBitmap.Width:= PaintBox1.ClientWidth; FBitmap.Height:= PaintBox1.ClientHeight; Screen.Cursor:= crHourglass; try If not GetDIBInBands( FFileName, FBitmap, 100 * 1024, bmw, bmh ) then exit; // 100k of buffer Label1.Caption:= Format( '%d X %d', [bmw, bmh] ); finally Screen.Cursor:= crDefault; end; end; end. object Form1: TForm1 Left = 264 Top = 146 Width = 696 Height = 480 Caption = 'Big Bitmap Viewer' Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -13 Font.Name = 'MS Sans Serif' Font.Style = [] OldCreateOrder = False WindowState = wsMaximized OnCreate = FormCreate OnDestroy = FormDestroy OnResize = FormResize PixelsPerInch = 120 TextHeight = 16 object PaintBox1: TPaintBox Left = 0 Top = 37 Width = 688 Height = 403 Align = alClient OnPaint = PaintBox1Paint end object Panel1: TPanel Left = 0 Top = 0 Width = 688 Height = 37 Align = alTop TabOrder = 0 object Label1: TLabel Left = 148 Top = 12 Width = 301 Height = 16 AutoSize = False end object Button1: TButton Left = 12 Top = 8 Width = 121 Height = 25 Caption = 'Define Bitmap' TabOrder = 0 OnClick = Button1Click end end end |
conundrum
尊榮會員 發表:893 回覆:1272 積分:643 註冊:2004-01-06 發送簡訊給我 |
思考 手賤 亂 一下
引言: 掃描器上掃到的一個很大的bmp圖檔(超過50M),總顯示記憶體不足的ERROR除非是要原圖印刷廠用所以要高解析的圖 如果只要顯示於PC上就利用失真壓縮檔案 來快速顯示客端選擇 優點 快快快 缺點 選擇列印時 利用專業級排版繪圖軟體協助處理 巨集 比較複雜 但色票與OS色票問題或印表機意外問題 都可解決 自己寫AP沒有專業級的協助 優點 技術自己攪 缺點 慢中之慢 非常慢 色票 印表機 暫存問題 問題一籮筐 決對是挑戰的一門高深遊戲 除非是要攪成 友立或ㄚ逗比 摳煮駱等 在windows下顯示選擇 送指令至 蘋果 去檔案伺服器抓圖用 ㄚ逗比 使用巨集 給他 專業的 硬 出來 還好是只用一般掃瞄器A4~A3 如果是高速雷射掃瞄器 或 巴士車身廣告看版的圖 一張100~600mb都是很正常ㄟ 引言: 若換為JPEG 格式,就可能不會了那甲ㄟ 那就使用jpeg2000 如 wameng 版主 所說 http://delphi.ktop.com.tw/topic.php?TOPIC_ID=38057 http://delphi.ktop.com.tw/topic.php?topic_id=26603 純哈啦 別K庵喔 發表人 - conundrum 於 2005/02/15 23:02:09 |
sam_000
一般會員 發表:27 回覆:47 積分:14 註冊:2003-09-15 發送簡訊給我 |
|
yorkland
高階會員 發表:2 回覆:138 積分:108 註冊:2004-12-17 發送簡訊給我 |
我的工作需要, 我有寫一個秀大圖的程式。
最初的版本也是用TImage來秀圖, 也遇到你提的問題, 但我記得當時遇到的狀況是以Delphi 5.0, 後來我改用Delphi 6.0後的版本, 就沒有遇到你提的問題。
不過要載入後才能秀圖, 的確效率還是很差, 而且我的程式中還需要放大縮小的功能, 所以現在的版本是以記憶體載入資料檔後, 畫面只繪製看得到的部份。 如果是1024*768, 我們就只要抓出需要的Pixel's data, 然後Run time組成一個新的Bmp Object, 然後再利用Canvas.Draw的方式畫到顯示的Canvas上。
|
sam_000
一般會員 發表:27 回覆:47 積分:14 註冊:2003-09-15 發送簡訊給我 |
|
yorkland
高階會員 發表:2 回覆:138 積分:108 註冊:2004-12-17 發送簡訊給我 |
|
bugmans
高階會員 發表:95 回覆:322 積分:188 註冊:2003-04-12 發送簡訊給我 |
今天在網路上閒逛時找到的元件,可以顯示超大的bmp圖檔,測試開啟3000*3000 25MB的圖檔
只有一開始載入時比較慢,圖顯示之後調整視窗大小速度就不受影響 我回頭找ktop相關討論,發現網友wameng將其中的原始碼貼出來,但沒有提供zip檔的下載連結 http://www.torry.net/quicksearchd.php?String=big&Title=Yes Big Bitmap Loader Sample v.1.0 By EzSoft Engineering. This sample application can load a bigmap of 2GB in only 100KB of memory. This solves the big bitmap problem in Win 9x and any other Windows version. 下載位址 http://www.torry.net/samples/samples/graphics/bigbitmapviewer.zip 至於這個元件在http://delphi.ktop.com.tw/board.php?cid=161&fid=110&tid=4043有介紹過 只是我一直搞不定DsgnIntf的錯誤,無法測試載入的速度為何 Big Bitmap Viewer v.1.01 By Grahame Marsh. "This component came about because I wanted to display 4000 x 4000 x 256 colour bitmaps (about 16MB in size). Using a TBitmap and a TImage took ages to load the images as a whole load of disc-swap file activity took place. The answer was to use a memory mapped file and the StretchDIBits API call which takes a memory pointer to the bitmap data, and doesn't realise (of course) that it's a memory mapped file. Load times and resource used drastically reduced." |
bugmans
高階會員 發表:95 回覆:322 積分:188 註冊:2003-04-12 發送簡訊給我 |
http://delphi.ktop.com.tw/board.php?cid=168&fid=921&tid=52469
wearefamily網友提到的 找一本 "Delphi 於繪圖與圖形處理上的實習應用" 裡面有一個TBigBitmap,可以解決這個問題 在http://www.asahi-net.or.jp/~HA3T-NKMR/DGS/DownLoad.htm可以下載 http://www.asahi-net.or.jp/~HA3T-NKMR/DGS/DHGL/dhgl1.2.lzh
編輯記錄
bugmans 重新編輯於 2008-04-20 18:14:28, 註解 無‧
|
本站聲明 |
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。 2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。 3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇! |