BSOne.SFC/EM.Lib/ImageEn_SRC/Demos/ImageEditing/ThreadedProcessing/Unit1.pas

686 lines
25 KiB
Plaintext

(* ------------------------------------------------------------------------------
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.