Jeg vil i dette indlæg behandle lidt forskelligt. Dels vil jeg vise, hvordan man tilgår data i en TBitmap ved hjælp af "Scanlines", og dels vil jeg vise hvordan man får Windows til at genere thumbnails for billeder der ligger på harddisken.
Jeg vil angive nogle eksekverings tider, det er tider tager fra min egen udviklings-pc. Det er ikke nogen super hurtigt maskine, men blot en ganske almindelig pc:
Processor : AMD Athlon II x2 255. 3.1 GHz.
Ram : 4 GB.
OS: Windows 7 64 bit
HD hastighed: 5200 omdrejninger
... altså ikke noget stort og voldsomt.
For at genere nogle thumbnails, skal jeg have noget at genere dem af. Til det formål har jeg skrevet en procedure, der laver en bitmap på størrelse med skærmen, bestående af random pixels. Som det fremgår af koden, bruger jeg "Scanline-metoden" til at tilgå de enkelte pixels.
De enkelte steps er forklaret i koden.
procedure GeneratePictures(Antal: Integer);
Herefter er det blot at kalde koden:var bmpWidth, bmpHeight, i, j: Integer; Bitmap: TBitmap; OutputDir: String; Scanline: pScanline; begin bmpWidth := GetSystemMetrics(SM_CXSCREEN); bmpHeight := GetSystemMetrics(SM_CYSCREEN); //opret en 32 bits bitmap Bitmap := TBitmap.Create; Bitmap.Width := bmpWidth; Bitmap.Height := bmpHeight; Bitmap.PixelFormat := pf32bit; OutputDir := ExtractFilePath(ParamStr(0)) + 'Images\'; ForceDirectories(OutputDir); while Antal > 0 do begin for i := 0 to bmpHeight - 1 do begin j := 0; //Hent adressen til den første pixel i linje "i" Scanline := Bitmap.Scanline[i]; while j < bmpWidth do begin //Beregn adressen på den pixel du vil tilgå: //Adressen på Pixel #0 i din linje gange antal pixel gange med størrelsen på en pixel with pRGBQuad(Integer(@Scanline[0]) + (j * Sizeof(TRGBQuad)))^ do begin //farvelæg din pixel rgbBlue := Random(255); rgbGreen := Random(255); rgbRed := Random(255); rgbReserved := 0; end; inc(j); //næste pixel end; end; //Dumme billeder har dumme navne ;o)Bitmap.SaveToFile(OutputDir + IntToStr(Antal) + '.bmp'); Dec(Antal); end; FreeAndNil(Bitmap); end;
En ganske uvidenskabelig test på min computer viser, at jeg skal bruge omkring 1200-1400 millisekunder på at lave og gemme de 50 billeder.procedure TMainForm.Button1Click(Sender: TObject); var Start, Stop: DWORD; begin Start := GetTickCount; GeneratePictures(50); Caption := IntToStr(GetTickCount - Start) end;
Nu hvor billederne er genereret, skal der laves thumbnails. I stedet for at "hive dem op" i CPU'en og lade dén beregne på det, kan man få Windows til at gøre det. Det er ganske hurtigt. Eneste "ulempe" er, at sådan noget kode der interagerer med Windows shell'en, dels ikke er ret kønt og dels ikke er ret læsevenligt. Jeg har dog gjort det nemt at bruge.
Jeg vil ikke bruge en masse plads på at forklare mig her, idet jeg har sat kommentarer i koden, og hvis det virkelig interesserer nogen hvad de enkelte Windows funktioner laver, kan jeg så afgjort anbefale at slå det op i MSDN.
unit FileThumbU; interface uses Graphics; Type TFileThumb = Class protected FhImageList48: Cardinal; FSize, FIconSize: Integer; FBmp: TBitmap; FFilePath: String; procedure SetFile(const Value: String); procedure SetSize(Value: Integer); procedure GetThumb; public property Size: Integer read FSize write SetSize; property ThumbBmp: TBitmap read FBmp; property FilePath: String read FFilePath write SetFile; Constructor Create; Destructor Destroy; override; end; implementation uses Windows, SysUtils, ActiveX, ShellApi, commCtrl, ShlObj; // http://msdn.microsoft.com/en-us/library/windows/desktop/bb761848(v=vs.85).aspx // IExtractImage Interface er implementeret i Windows' Shell32.dll (fra version 4.70) type IExtractImage = interface ['{BB2E617C-0920-11d1-9A0B-00C04FC2D6C1}'] function GetLocation(pszwPathBuffer: PWideChar; cch: DWORD; var dwPriority: DWORD; var rgSize: TSize; dwRecClrDepth: DWORD; var dwFlags: DWORD): HResult; stdcall; function Extract(var hBmpThumb: HBITMAP): HResult; stdcall; end; const MinSize = 52; MaxSize = 256; ColorFormat: DWORD = 24; IEIFLAG_OFFLINE = 8; IEIFLAG_SCREEN = $20; Constructor TFileThumb.Create; var hImagList16, hImagList32: Cardinal; pfsi: TShFileInfo; icHgt, icWid: Integer; begin //Default thumbnail størrelse FSize := 100; FFilePath := ''; { Henter et handle til den imagelist, som Windows internt bruger til Iconer. Yderligere information: http://msdn.microsoft.com/en-us/library/bb762179(VS.85).aspx Parameteren SHGFI_SYSICONINDEX er den centrale } hImagList32 := SHGetFileInfo('dummy.txt', FILE_ATTRIBUTE_NORMAL, pfsi, SizeOf(pfsi), SHGFI_LARGEICON or SHGFI_SYSICONINDEX or SHGFI_USEFILEATTRIBUTES); hImagList16 := SHGetFileInfo('dummy.txt', FILE_ATTRIBUTE_NORMAL, pfsi, SizeOf(pfsi), SHGFI_SMALLICON or SHGFI_SYSICONINDEX or SHGFI_USEFILEATTRIBUTES); FhImageList48 := hImagList16 + (hImagList16 - hImagList32); if ImageList_GetIconSize(FhImageList48, icHgt, icWid) and (icHgt = 48) then FIconSize := 48 else begin FhImageList48 := hImagList32; if ImageList_GetIconSize(hImagList32, icHgt, icWid) then FIconSize := icHgt else FIconSize := 32; end; FBmp := Graphics.TBitmap.Create; FBmp.Canvas.Brush.Color := GetSysColor(COLOR_WINDOW); FBmp.Width := FSize; FBmp.Height := FSize; FBmp.PixelFormat := pf24Bit; end; destructor TFileThumb.Destroy; begin FreeAndNil(FBmp); inherited Destroy; end; procedure TFileThumb.SetFile(const Value: String); begin if Value = FFilePath then Exit; FFilePath := Value; GetThumb; end; procedure TFileThumb.SetSize(Value: Integer); begin if Value = FSize then Exit; if Value > MaxSize then FSize := MaxSize else if Value < MinSize then FSize := MinSize else FSize := Value; FBmp.Width := FSize; FBmp.Height := FSize; GetThumb; end; procedure TFileThumb.GetThumb; var Path, Name: String; FolderISF, DesktopISF: IShellFolder; IExtractImg: IExtractImage; Attrib, Eaten: DWORD; pItemIDL: PItemIDList; MemAlloc: IMalloc; CharBuf: array [0 .. 2047] of WideChar; hBmp: HBITMAP; Size1: TSize; Priority, Flags: Cardinal; GLResult: HResult; Mid: Integer; ShInfo1: TShFileInfo; begin FBmp.Width := FSize; FBmp.Height := FSize; if (FFilePath = '') or (not FileExists(FFilePath)) then begin FBmp.Canvas.Rectangle(0, 0, FSize, FSize); Exit; end; Path := ExtractFilePath(FFilePath); Name := ExtractFileName(FFilePath); Mid := (FSize shr 1) - (FIconSize shr 1); FBmp.Canvas.Rectangle(-1, -1, FSize + 3, FSize + 3); //Lad Windows shell'en til at genere en thumbnail SHGetFileInfo(PChar(FFilePath), FILE_ATTRIBUTE_NORMAL, ShInfo1, SizeOf(ShInfo1), SHGFI_LARGEICON or SHGFI_SYSICONINDEX); //Tegn den på bitmap'en ImageList_Draw(FhImageList48, ShInfo1.iIcon, FBmp.Canvas.Handle, Mid, Mid, ILD_TRANSPARENT); // Krav: Mindst Windows 2000 if (Win32Platform <> VER_PLATFORM_WIN32_NT) or (Win32MajorVersion < 6) then Exit; if (SHGetMalloc(MemAlloc) <> NOERROR) or (MemAlloc = nil) or (SHGetDesktopFolder(DesktopISF) <> NOERROR) then Exit; //Oversæt Path til PItemIDList, den skal vi bruge senere if DesktopISF.ParseDisplayName(0, nil, StringToOleStr(Path), Eaten, pItemIDL, Attrib) <> NOERROR then Exit; //Bind vores PItemIDList til Windows Shell DesktopISF.BindToObject(pItemIDL, nil, IShellFolder, FolderISF); MemAlloc.Free(pItemIDL); //Oversæt navn til PItemIDList, den skal vi bruge senere if FolderISF.ParseDisplayName(0, nil, StringToOleStr(Name), Eaten, pItemIDL, Attrib) <> NOERROR then Exit; //Tag fat i billedet og generer en thumbnail FolderISF.GetUIObjectOf(0, 1, pItemIDL, IExtractImage, nil, IExtractImg); MemAlloc.Free(pItemIDL); if IExtractImg = nil then Exit; Size1.cx := FSize; Size1.cy := FSize; Flags := IEIFLAG_SCREEN or IEIFLAG_OFFLINE; Priority := 0; //Hent path til vores billede, og vær sikker på at vores Thumbnail bitmap har det rigtige handle GLResult := IExtractImg.GetLocation(CharBuf, SizeOf(CharBuf), Priority, Size1, ColorFormat, Flags); if (GLResult = NOERROR) or (GLResult = E_PENDING) then begin if (IExtractImg.Extract(hBmp) <> NOERROR) or (hBmp = 0) then Exit; FBmp.Handle := hBmp; end; end; end.
Jeg sagde jo koden ikke var ret køn ;o) Men som det fremgår af eksemplet, har jeg lagt det i en unit for sig selv, således at det er "pakket" væk.
Tilbage er der blot at kalde koden:
uses FileThumbU, IOUtils; procedure TMainForm.Button2Click(Sender: TObject); var Start, Stop: DWORD; BitmapFile, NewFileName: String; begin Start := GetTickCount; for BitmapFile in TDirectory.GetFiles(ExtractFilePath(ParamStr(0)) + 'Images\', '*.bmp') do With TFileThumb.Create do try NewFileName := ExtractFilePath(BitmapFile) + 'th_' + ExtractFileName(BitmapFile); FilePath := BitmapFile; ThumbBmp.SaveToFile(NewFileName); finally Free; end; Caption := IntToStr(GetTickCount - Start) end;
Tests fortaget på min computer viser at det tager ca. 1400-1500 millisekunder at udføre.
Den opmærksomme læser bemærker sikkert, at jeg benytter mig af IOUtils.pas i eksempelt, som først er tilgængelig i Delphi 2009. Det er gjort fordi det er en nem måde at traversere en folder igennem efter filer. Det er så også det eneste kode, der ikke kan køre på en ældre version af Delphi. Skulle nogen få brug for det, kan man nemt selv omskrive til et "FindFirst .. FindNext .. FindClose"-loop, som man brugte før i tiden.
Så alt i alt har jeg genereret 50 billeder med random pixels, smidt dem på harddisken og lavet thumbnails ud af dem på under 3 sekunder. Ikke dumt når man tager billedernes størrelse i betragtning.
Thumbnail-generatoren kan også bruges på JPEGs og andre formater som din Windows kan lave thumbnails af. Under alle omstændigheder får man en Bitmap ud af det, som så selv må lave om til det ønskede format. I mit eksempel har jeg blot holdt mig til bitmaps for ikke at sløre billedet.
Jens Borrisholt
Ingen kommentarer:
Send en kommentar