(* ------------------------------------------------------------------------------ TImageEnView Image Processing in a TThread : 1.0 For A Programmers Introduction To Using ImageEn Library Suite Copyright © 2013 : Copyright Adirondack Software & Graphics Last Modification : 06-19-2013 Source File : Unit1.pas Compiler : Delphi 2010 ImageEnVersion : 4.3.1 Operating System : Windows 7 Comment : This demo shows how to process images in a secondary thread : Multi-Threading : Multi-threading in Delphi lets you create applications that include : several simultaneous paths of execution or processes. A "normal" : Delphi application is single-threaded: all (VCL) objects access their : properties and execute their methods within this single thread usually : referred to as the main thread. : : Secondary Thread : To speed up data processing in your application you can decide to : include one or more "secondary" threads. When several threads are : running in the application a question arises: how to update your GUI : (user interface) as a result of a thread execution. : : GUI : The answer to the question lies in the TThread class Synchronize method. : To update your application's user interface run in the main thread : from a secondary thread you need to call the TThread.Synchronize : method - a thread-safe method that avoids multi-threading conflicts : that can arise from accessing object properties or methods that are : not thread-safe, or using resources not in the main thread of : execution. : : An active GUI is maintained in this demo while doing processor : intensive image processing in the background. The five image : processing procedures run in this example in sequence are AutoImageEnhance, : AdjustGainOffset, AutoSharp, HistAutoEqualize and (optionally) Negative. : In a non-threaded Delphi application these procedures run in succession : would take several seconds to complete depending on the size of the image : file and the processor used. While the image processing procedures are : running the application would act like the GUI is locked. It would be : impossible to cancel the operation or resize the form because the GUI : would be unresponsive to button clicks, key presses or mouse actions. : The same procedures processed in a secondary thread allow the GUI to remain : responsive and not interfere with the image processing. : : Synchronize : In this demo all image processing is performed in a secondary thread : and all GUI elements (TForm, TLabel, TButton, TProgressBar and : TImageEnView are all updated in the main thread by using the TThread : Synchronize method. : : Usage : When processing an image in the secondary thread, the processing may : be aborted at any time by pressing the Cancel button. If you resize : the form or view the about dialog while the processing thread is : running, the thread will not be interrupted because these things are : executed in the main thread, and image processing is executed in a : secondary thread. : : What You Should Not Do : You should not directly access a VCL object or component directly in a : secondary thread. All calls to a VCL object or component must be made in : the main thread or by calling the procedure with TThread.Synchronize. : What this means is if you call ImageEnView1.LayersMergeAll or : ImageEnView1. LayersDrawTo or any other VCL component call with Syncronize, : the call will be executed in the main thread and not in the secondary : thread. In essence then, any call made in Syncronize will not be threaded : and will act as though you made the call in the main thread. : : You should never call ImageEnView1.LayersMergeAll or : ImageEnView1.LayersDrawTo in the secondary thread. This will lead to : potential threading conflicts, exceptions and/or memory loss. : : In essence then, to create a truly threaded image processing operation : ImageEnView1.Proc.Sharpen, ImageEnView1.Proc.AutoImageEnhance or any other : ImageEnView1.Proc should never be called from within the secondary thread. : Instead create a TimageEnProc component at run time within the thread and : then update the ImageEnView1 on the main form by assigning the TIEBitmap : that is modified by the procedure in the secondary thread to the : Form1.ImageEnView by calling Synchronize. This file is copyright (C) W W Miller, 1986-2013. It may be used without restriction. This code distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. ------------------------------------------------------------------------------ *) unit Unit1; {$I ie.inc} interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, ExtDlgs, ComCtrls, imageenproc, ieview, imageenview, imageenio, hyiedefs, hyieutils, Menus, iexBitmaps, iesettings, iexLayers, iexRulers; type { A TThread descendent for Image Processing } TProcessingThread = class(TThread) private { Private declarations } procedure ImageEnProcFinishWork(Sender: TObject); procedure ImageEnProcProgress(Sender: TObject; per: Integer); protected { Protected declarations } procedure Execute; override; public { Public declarations } constructor Create(CreateSuspended: Boolean); end; TForm1 = class(TForm) Panel1: TPanel; Process1: TButton; Cancel1: TButton; Open1: TButton; OpenPictureDialog1: TOpenPictureDialog; Fit1: TCheckBox; Panel3: TPanel; Undo1: TButton; Exit1: TButton; ImageEnView1: TImageEnView; StatusBar1: TStatusBar; ProcessingPanel1: TPanel; LabelThread1: TLabel; LabelProcedure1: TLabel; Panel4: TPanel; ProgressBar1: TProgressBar; Information1: TButton; procedure Process1Click(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Cancel1Click(Sender: TObject); procedure FormCreate(Sender: TObject); procedure Open1Click(Sender: TObject); procedure Fit1Click(Sender: TObject); procedure Undo1Click(Sender: TObject); procedure Exit1Click(Sender: TObject); procedure ImageEnView1Resize(Sender: TObject); procedure ImageEnView1FinishWork(Sender: TObject); procedure ImageEnView1Progress(Sender: TObject; per: Integer); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure Information1Click(Sender: TObject); private { Private declarations } public { Public declarations } AThread: TProcessingThread; { Image processing thread } { Image processing thread terminate event } procedure ThreadTerminate(Sender: TObject); end; var Form1: TForm1; implementation {$R *.dfm} uses Unit2; { ------------------------------Global functions--------------------------------------------- } function AddThousandSeparator(const AString: string; const AChar: Char): string; { Add a specified thousand separator in the correct location in a string. } var i: integer; { Loop through separator position } begin Result := AString; i := Length(AString) - 2; while i > 1 do begin Insert(AChar, Result, i); i := i - 3; end; end; function AddDefThousandSeparator(const AString: string): string; { Add the local default thousand separator in the correct location in a string. } begin // Note: FormatSettings.ThousandSeparator in XE8 or Newer Result := AddThousandSeparator(AString, {$IFDEF DelphiXE8orNewer} FormatSettings.ThousandSeparator {$ELSE} SysUtils.ThousandSeparator {$ENDIF}); end; function IntegerToStr(const i: Int64): string; { Add the local default thousand separator in the correct location in a number and returns a string. } begin Result := AddDefThousandSeparator(IntToStr(i)); end; function EllipsifyText(const AsPath: Boolean; const AText: string; const ACanvas: Graphics.TCanvas; const MaxWidth: Integer): string; { Ellipisfy AText and return the text as a ellipisfied text string. } var TempPChar: PChar; { temp buffer to hold text } TempRect: TRect; { temp rectangle hold max width of text } Params: UINT; { flags passed to DrawTextEx API function } begin { Alocate mem for PChar } GetMem(TempPChar, (Length(AText) + 1) * SizeOf(Char)); try { Copy Text into PChar } TempPChar := SysUtils.StrPCopy(TempPChar, AText); { Create Rectangle to Store PChar } TempRect := Classes.Rect(0, 0, MaxWidth, High(Integer)); { Set Params depending wether it's a path or not } if AsPath then Params := Windows.DT_PATH_ELLIPSIS else Params := Windows.DT_END_ELLIPSIS; { Tell it to Modify the PChar, and do not draw to the canvas } Params := Params + Windows.DT_MODIFYSTRING + Windows.DT_CALCRECT; { Ellipsify the string based on availble space to draw in } Windows.DrawTextEx(ACanvas.Handle, TempPChar, -1, TempRect, Params, nil); { Copy the modified PChar into the result } Result := SysUtils.StrPas(TempPChar); finally { Free Memory from PChar } FreeMem(TempPChar, (Length(AText) + 1) * SizeOf(Char)); end; end; { TProcessingThread } constructor TProcessingThread.Create(CreateSuspended: Boolean); { Create the thread. } begin inherited; end; procedure TProcessingThread.ImageEnProcProgress(Sender: TObject; per: Integer); { Set the progressbar position. } begin Synchronize( procedure begin Form1.ProgressBar1.Position := per; { Ensure progress is painted when themes are enabled } Form1.ProgressBar1.Position := per - 1; end); end; procedure TProcessingThread.ImageEnProcFinishWork(Sender: TObject); { Reset the progressbar position. } begin Synchronize( procedure begin Form1.ProgressBar1.Position := 0; end); end; procedure TProcessingThread.Execute; { Process the image in the thread. Note: All references to GUI elements must occur inside a Synchronize method. Synchronize suspends the current thread and has the main thread call the procedure. When the procedure finishes, control returns to the current thread. The actual image processing is done by an imageenproc (non GUI element) directly in the thread. All of the GUI elements are accessed with Synchronize.} var iImageEnProc: TImageEnProc; iIEBitmap: TIEBitmap; begin inherited; { Free the thread onTerminate } FreeOnTerminate := True; { Set the LabelThread caption ------------------------------------------------------------------ } if not Terminated then begin { Synchronize GUI elements } Synchronize( procedure begin Form1.Cancel1.Enabled := True; Form1.Cancel1.Update; Form1.LabelThread1.Caption := 'Thread is running...'; Form1.LabelThread1.Update; end); { Provide time to see the change } Sleep(500); end; { Get the IEBitmap from the main form----------------------------------------------------------- } if not Terminated then begin { Synchronize GUI elements } Synchronize( procedure begin iIEBitmap := TIEBitmap.Create(Form1.ImageEnView1.IEBitmap); end); try { Create a copy of ImageEnView bitmap } iImageEnProc := TImageEnProc.CreateFromBitmap(iIEBitmap); try { Turn off auto undo } iImageEnProc.AutoUndo := False; { Permit up to 5 undos - 5 filters are applied here so if you add more filters increase the undolimit to match. The filters or procedures run here are AutoImageEnhance, AdjustGainOffset, AutoSharp, HistAutoEqualize, and (optionally) Negative } iImageEnProc.UndoLimit := 5; { Set the OnProgress and OnFinishWork events } iImageEnProc.OnProgress := ImageEnProcProgress; iImageEnProc.OnFinishWork := ImageEnProcFinishWork; { AutoImageEnhance Filter --------------------------------------------------------Filter 1-- } if not Terminated then begin { Synchronize GUI elements } Synchronize( procedure begin Form1.LabelProcedure1.Caption := 'Applying Auto Image Enhance Filter...'; Form1.LabelProcedure1.Update; Form1.Undo1.Hint := 'Auto Image Enhance Filter'; Form1.ImageEnView1.Proc.SaveUndoCaptioned('Auto Image Enhance Filter'); Form1.StatusBar1.Panels[4].Text := 'Undo: ' + IntToStr(Form1.ImageEnView1.Proc.UndoCount); end); { Actual work done here in the secondary thread } iImageEnProc.AutoImageEnhance3; { Synchronize GUI elements } Synchronize( procedure begin Form1.ImageEnView1.IEBitmap.Assign(iIEBitmap); { update image seen by the user } Form1.ImageEnView1.Update; Application.ProcessMessages(); end); Sleep(500); { Provide time to see the change } end; { Adjust Gain Offset Filter ------------------------------------------------------Filter 2-- } if not Terminated then begin { Synchronize GUI elements } Synchronize( procedure begin Form1.LabelProcedure1.Caption := 'Adjusting Gain Offset Filter...'; Form1.LabelProcedure1.Update; Form1.Undo1.Hint := 'Agjust Gain Offset Filter…'; Form1.ImageEnView1.Proc.SaveUndoCaptioned('Adjust Gain Offset Filter'); Form1.StatusBar1.Panels[4].Text := 'Undo: ' + IntToStr(Form1.ImageEnView1.Proc.UndoCount); end); { Actual work done here in the secondary thread } iImageEnProc.AdjustGainOffset; { Synchronize GUI elements } Synchronize( procedure begin Form1.ImageEnView1.IEBitmap.Assign(iIEBitmap); { update image seen by the user } Form1.ImageEnView1.Update; Application.ProcessMessages(); end); Sleep(500); { Provide time to see the change } end; { AutoSharp ----------------------------------------------------------------------Filter 3-- } if not Terminated then begin { Synchronize GUI elements } Synchronize( procedure begin Form1.LabelProcedure1.Caption := 'Applying Auto Sharp Filter...'; Form1.LabelProcedure1.Update; Form1.Undo1.Hint := 'Applying Auto Sharp Filter…'; Form1.ImageEnView1.Proc.SaveUndoCaptioned('Hist Sharp Filter'); Form1.StatusBar1.Panels[4].Text := 'Undo: ' + IntToStr(Form1.ImageEnView1.Proc.UndoCount); end); { Actual work done here in the secondary thread } iImageEnProc.AutoSharp; { Synchronize GUI elements } Synchronize( procedure begin Form1.ImageEnView1.IEBitmap.Assign(iIEBitmap); { update image seen by the user } Form1.ImageEnView1.Update; Application.ProcessMessages(); end); Sleep(500); { Provide time to see the change } end; { HistAutoEqualize ---------------------------------------------------------------Filter 4-- } if not Terminated then begin { Synchronize GUI elements } Synchronize( procedure begin Form1.LabelProcedure1.Caption := 'Applying Hist Auto Equalize Filter...'; Form1.LabelProcedure1.Update; Form1.Undo1.Hint := 'Applying Hist Auto Equalize Filter…'; Form1.ImageEnView1.Proc.SaveUndoCaptioned('Hist Auto Equalize Filter'); Form1.StatusBar1.Panels[4].Text := 'Undo: ' + IntToStr(Form1.ImageEnView1.Proc.UndoCount); end); { Actual work done here in the secondary thread } iImageEnProc.HistAutoEqualize; { Synchronize GUI elements } Synchronize( procedure begin Form1.ImageEnView1.IEBitmap.Assign(iIEBitmap); { update image seen by the user } Form1.ImageEnView1.Update; Application.ProcessMessages(); end); Sleep(500); { Provide time to see the change } end; { Negative -----------------------------------------------------------------------Filter 5-- } if not Terminated then begin case MessageDlg('Do you want to apply a Negative Filter?', mtConfirmation, [mbYes, mbNo], 0) of mrYes: begin { Synchronize GUI elements } Synchronize( procedure begin Form1.LabelProcedure1.Caption := 'Applying Negative Filter...'; Form1.LabelProcedure1.Update; Form1.Undo1.Hint := 'Applying Negative Filter…'; Form1.ImageEnView1.Proc.SaveUndoCaptioned('Hist Negative Filter'); Form1.StatusBar1.Panels[4].Text := 'Undo: ' + IntToStr(Form1.ImageEnView1.Proc.UndoCount); end); { Actual work done here in the secondary thread } iImageEnProc.Negative; { Synchronize GUI elements } Synchronize( procedure begin Form1.ImageEnView1.IEBitmap.Assign(iIEBitmap); { update image seen by the user } Form1.ImageEnView1.Update; Application.ProcessMessages(); end); Sleep(500); { Provide time to see the change } end; mrNo: begin end; end; // case end; { Free iIEBitmap } finally iImageEnProc.Free; end; { Free iImageEnProc } finally iIEBitmap.Free; end; { Terminate the thread } Terminate; end; end; { ----------------------------------------TForm1-------------------------------------------- } procedure TForm1.Open1Click(Sender: TObject); { Open an image. } begin if OpenPictureDialog1.Execute then begin if FileExists(OpenPictureDialog1.Filename) then begin ImageEnView1.Clear; Caption := 'Processing An Image with ImageEn In A Thread The Delphi Way- ' + OpenPictureDialog1.Filename; ImageEnView1.IO.LoadFromFile(OpenPictureDialog1.Filename); { Display status information in the StatusBar } StatusBar1.Panels[0].Text := EllipsifyText(True, ExtractFileDir(OpenPictureDialog1.Filename), Canvas, StatusBar1.Panels[0].Width); StatusBar1.Panels[1].Text := EllipsifyText(False, ExtractFileName(OpenPictureDialog1.Filename), Canvas, StatusBar1.Panels[1].Width); StatusBar1.Panels[2].Text := 'Width: ' + IntegerToStr(ImageEnView1.IO.Params.Width); StatusBar1.Panels[3].Text := 'Height: ' + IntegerToStr(ImageEnView1.IO.Params.Height); if Fit1.Checked then ImageEnView1.Fit; ImageEnView1.Update; { Initialize the buttons } Cancel1.Enabled := False; Process1.Enabled := True; end; end; end; procedure TForm1.Process1Click(Sender: TObject); { Start the thread to begin processing images. } begin ProcessingPanel1.Color := $0000D7FF; LabelThread1.Color := clWhite; LabelProcedure1.Color := clWhite; { Enable the cancel button } Cancel1.Enabled := True; Cancel1.Update; { Disable the Undo button } Undo1.Enabled := False; { Create the thread suspended } AThread := TProcessingThread.Create(True); { Set the threads OnTerminate event } AThread.OnTerminate := ThreadTerminate; { Set the Cancel button as the focused control } Cancel1.SetFocus; { Disable the Process button } Process1.Enabled := False; { Start the thread } AThread.Start; { Processing will occur in the TProcessingThread.Execute event } end; procedure TForm1.ThreadTerminate(Sender: TObject); { When the thread terminates set the label captions and button states. } var iObject: TObject; begin { Show a message if exception takes place in the thread } Assert(Sender is TThread); iObject := TThread(Sender).FatalException; if Assigned(iObject) then begin { Thread terminated due to an exception } if iObject is Exception then Application.ShowException(Exception(iObject)) else ShowMessage(iObject.ClassName); end else begin { Thread terminated without an exception } end; ProcessingPanel1.Color := clMoneyGreen; LabelThread1.Color := clBlack; LabelProcedure1.Color := clBlack; { Set the label captions } LabelThread1.Caption := 'Thread is not running...'; LabelProcedure1.Caption := 'Image Processing Complete...'; { Enable the Undo button } Undo1.Enabled := True; { Disable the Cancel button } Cancel1.Enabled := False; { Enable the Process button } Process1.Enabled := True; ProgressBar1.Position := 0; StatusBar1.Panels[4].Text := 'Undo: ' + IntToStr(ImageEnView1.Proc.UndoCount); { Free the AThread } AThread := nil; end; procedure TForm1.Undo1Click(Sender: TObject); { Undo the last change. } begin Screen.Cursor := crHourglass; try LabelProcedure1.Caption := 'Undoing ' + Undo1.Hint; LabelProcedure1.Update; Sleep(250); ImageEnView1.Proc.Undo(); ImageEnView1.Proc.ClearUndo; Undo1.Hint := ImageEnView1.Proc.UndoCaptions[0]; Undo1.Enabled := ImageEnView1.Proc.CanUndo; LabelProcedure1.Caption := ''; StatusBar1.Panels[4].Text := 'Undo: ' + IntToStr(ImageEnView1.Proc.UndoCount); finally Screen.Cursor := crDefault; end; end; procedure TForm1.Exit1Click(Sender: TObject); { Exit the application. } begin Close; end; procedure TForm1.Fit1Click(Sender: TObject); { Fit the image. } begin if Fit1.Checked then ImageEnView1.Fit else ImageEnView1.Zoom := 100; end; procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); { If the thread is assigned, then terminate and free the thread. } begin if Assigned(AThread) then begin AThread.Terminate; end; end; procedure TForm1.FormCreate(Sender: TObject); { Initialize variables and create the Thread. } begin { Register imageen supported file types with vcl } IERegisterFormats; { Set the OpenPicture Filter } OpenPictureDialog1.Filter := GraphicFilter(TGraphic); Cancel1.Enabled := False; Process1.Enabled := False; ImageEnView1.Proc.AutoUndo := False; ImageEnView1.Proc.UndoLimit := 99; Undo1.Enabled := ImageEnView1.Proc.CanUndo; LabelThread1.Caption := 'Thread is Not Running'; end; procedure TForm1.FormDestroy(Sender: TObject); { Form Destroy. } begin IEUnregisterFormats; end; procedure TForm1.ImageEnView1FinishWork(Sender: TObject); { Reset the progressbar. } begin ProgressBar1.Position := 0; end; procedure TForm1.ImageEnView1Progress(Sender: TObject; per: Integer); { Set the progressbar. } begin ProgressBar1.Position := per; ProgressBar1.Position := per - 1; ProgressBar1.Position := per; end; procedure TForm1.ImageEnView1Resize(Sender: TObject); { If Fit1 is checked then fit. } begin if Fit1.Checked then ImageEnView1.Fit; end; procedure TForm1.Information1Click(Sender: TObject); { Display the information form. } begin Form2 := TForm2.Create(self); try Form2.ShowModal; finally Form2.Free; end; end; procedure TForm1.Cancel1Click(Sender: TObject); { Cancel the thread. } begin AThread.Terminate; if AThread.Terminated then begin Cancel1.Enabled := False; Process1.Enabled := True; LabelThread1.Caption := 'Thread is Not Running...'; end; end; end.