T O P I C R E V I E W |
cpstevenc |
Posted - Apr 23 2013 : 09:13:12 Version 4.3.0
I have a deal where I hit up a database to that gives me a list of images and where to grab them.
Using a TImageEnMView.
I will traverse the database to get a list of file paths.
I will then start adding them to the TImageEnMView one by one. If an image doesn't exist, i show a dummy image in its place.
If there is alot of images, and accessing a share, its slow. Real slow.
SOOO, is there a preferred method of moving this action to a back ground thread, to get a list of images into memory, all their text, colors, whatever done, then plop them into the TImageEnMView ?
The same would go for a TImageEnView , as I will have a "primary" image given to me which can be big-ish.. so like to load that up in background and then show it once loaded.
I was requested to try and keep the main UI lock up free while loading these images from their sources.
|
11 L A T E S T R E P L I E S (Newest First) |
cpstevenc |
Posted - Nov 13 2013 : 15:10:14 Thanks Ill check this out when I get a chance.. I didn't look it over much but assume this will work with D7? |
w2m |
Posted - Oct 01 2013 : 09:40:02 Over the summer I became more proficient writing Delphi threads with imageen so another alternative is shown below. I am not sure how this will work on a network or how many images you are loading, but in my testing here 50, 18.1mp. photographs are loaded nearly instantly and you do not loose access to the GUI.
This approach is similar to that suggested by Fabrizio, except the images are added to ImageEnMView with AppendImage from a stringlist containing the file paths in a thread. The speed is similar to FillFromDirectory because FillFromDirectory is threaded.
For a database application add the filenames from the database to the stringlist something like this:
{ Get the filenames from the database }
AStringList.Add(t.Item[x].Location); Here I assume that the tImageDataCollection is already created and accessible to the thread. If the tImageDataCollection is not accessible in the thread then you will have to put the code in a Syncronize block to access it from the main form: { Get the file list from the database on the main form }
Synchronize(
procedure
begin
AStringList.Add(Form1.t.Item[x].Location);
end);
Let me know if this works for you.
unit Unit1;
{$WARN SYMBOL_PLATFORM OFF}
{$WARN UNIT_PLATFORM OFF}
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
imageenview, Vcl.ExtCtrls, iemio, ieview, iemview, Vcl.StdCtrls,
Vcl.ComCtrls;
type
{ A TThread descendent for Loading an Image }
TImageLoadingThread = class(TThread)
private
{ Private declarations }
protected
{ Protected declarations }
procedure Execute; override;
public
{ Public declarations }
constructor Create(CreateSuspended: Boolean);
end;
TForm1 = class(TForm)
ImageEnMView1: TImageEnMView;
ImageEnMIO1: TImageEnMIO;
Panel1: TPanel;
Splitter1: TSplitter;
ImageEnView1: TImageEnView;
FillWithThread1: TButton;
Folder1: TEdit;
Browse1: TButton;
FillFromFolder1: TButton;
Fit1: TCheckBox;
Clear1: TButton;
StatusBar1: TStatusBar;
procedure FormCreate(Sender: TObject);
procedure Browse1Click(Sender: TObject);
procedure FillFromFolder1Click(Sender: TObject);
procedure FillWithThread1Click(Sender: TObject);
procedure Clear1Click(Sender: TObject);
procedure ImageEnMView1AllDisplayed(Sender: TObject);
procedure Folder1Change(Sender: TObject);
procedure ImageEnMView1ImageSelect(Sender: TObject; idx: Integer);
procedure Fit1Click(Sender: TObject);
private
{ Private declarations }
AImageLoadingThread: TImageLoadingThread;
procedure ThreadTerminate(Sender: TObject);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses Vcl.FileCtrl, Winapi.ShlObj, Winapi.ShellApi, hyieutils, imageenio;
{ Global }
function DesktopFolder: string;
{ Find Desktop folder location }
var
iR: Bool;
iPath: array [0 .. MAX_PATH] of Char;
begin
iR := Winapi.ShlObj.ShGetSpecialFolderPath(0, iPath, CSIDL_DESKTOP, False);
if not iR then
raise Exception.Create('Could not find Desktop folder location.');
Result := IncludeTrailingPathDelimiter(iPath);
end;
function BrowseForFolder(AFolder: string): string;
{ BrowseForFolder }
begin
if System.SysUtils.DirectoryExists(AFolder) then
if Vcl.FileCtrl.SelectDirectory('Select Folder', 'Desktop', AFolder) then
Result := AFolder;
end;
function GetAllFiles(AMask: string; AStringList: TStringList): Integer;
{ Get all supported image files matching mask (*.*.. '*.jpg') and add them to a stringlist }
var
iSearch: TSearchRec;
iDirectory: string;
iCount: Integer;
iFileAttrs: Integer;
begin
iCount := 0;
iDirectory := ExtractFilePath(AMask);
iFileAttrs := faArchive - faHidden;
{ Find all files matching AMask }
if FindFirst(AMask, iFileAttrs, iSearch) = 0 then
begin
repeat
if IsKnownFormat(iDirectory + iSearch.name) then
begin
{ Add the imageenio supported files to the stringlist }
AStringList.Add(iDirectory + iSearch.name);
Inc(iCount);
end;
until FindNext(iSearch) <> 0;
end;
{ Subdirectories }
if FindFirst(iDirectory + '*.*', faDirectory, iSearch) = 0 then
begin
repeat
if ((iSearch.Attr and faDirectory) = faDirectory) and
(iSearch.name[1] <> '.') then
GetAllFiles(iDirectory + iSearch.name + '\' + ExtractFileName(AMask),
AStringList);
until FindNext(iSearch) <> 0;
FindClose(iSearch);
end;
Result := iCount;
end;
{ TImageLoadingThread }
constructor TImageLoadingThread.Create(CreateSuspended: Boolean);
{ Create the thread. }
begin
inherited;
end;
procedure TImageLoadingThread.Execute;
{ Load images ImageENMView 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. }
var
i: Integer;
iFolder: string;
iFileCount: Integer;
iStringList: TStringList;
begin
inherited;
{ Free the thread onTerminate }
FreeOnTerminate := True;
if not Terminated then
begin
{ Get the foldername }
Synchronize(
procedure
begin
iFolder := IncludeTrailingPathDelimiter(Form1.Folder1.Text);
end);
{ Create a string list to hold the filenames }
iStringList := TStringList.Create;
try
iFileCount := GetAllFiles(iFolder + '*.*', iStringList);
if iFileCount > 0 then
begin
for i := 0 to iFileCount - 1 do
{ Add the images to ImageEnMView }
Synchronize(
procedure
begin
if iFileCount > 0 then
begin
Form1.ImageEnMView1.ImageFileName
[Form1.ImageEnMView1.AppendImage()] := iStringList.Strings[i];
end;
end);
end
else
MessageBox(0, 'No supported images were found', 'Warning',
MB_ICONWARNING or MB_OK);
finally
iStringList.Free;
end;
end;
{ Terminate the thread }
Terminate;
end;
{ TForm1 }
procedure TForm1.Folder1Change(Sender: TObject);
{ Folder1Change }
begin
StatusBar1.Panels[0].Text := Folder1.Text;
end;
procedure TForm1.FormCreate(Sender: TObject);
{ Set parameters }
begin
ImageEnMView1.SetModernStyling(True, 225, 225);
Folder1.Text := DesktopFolder;
end;
procedure TForm1.ImageEnMView1AllDisplayed(Sender: TObject);
{ ImageEnMView1AllDisplayed }
begin
ImageEnMView1.SelectedImage := 0;
StatusBar1.Panels[2].Text := 'Images: ' + IntToStr(ImageEnMView1.ImageCount);
end;
procedure TForm1.ImageEnMView1ImageSelect(Sender: TObject; idx: Integer);
{ ImageEnMView1ImageSelect }
var
iFilename: string;
begin
iFilename := ImageEnMView1.ImageFileName[idx];
ImageEnView1.IO.LoadFromFile(iFilename);
ImageEnView1.AutoFit := Fit1.Checked;
if Fit1.Checked then
ImageEnView1.Fit;
end;
procedure TForm1.Browse1Click(Sender: TObject);
{ Browse for a folder }
var
iFolder: string;
begin
iFolder := BrowseForFolder(DesktopFolder);
Folder1.Text := IncludeTrailingPathDelimiter(iFolder);
end;
procedure TForm1.Clear1Click(Sender: TObject);
{ Clear }
begin
ImageEnMView1.Clear;
ImageEnView1.Clear;
Caption := 'Fill ImageEnMView In a Thread';
StatusBar1.Panels[1].Text := '';
StatusBar1.Panels[2].Text := '';
end;
procedure TForm1.FillFromFolder1Click(Sender: TObject);
{ Fill ImageEnMView with fill from directory }
var
iFilename: string;
begin
if System.SysUtils.DirectoryExists(Folder1.Text) then
begin
ImageEnMView1.Clear;
ImageEnView1.Clear;
Caption := 'Fill ImageEnMView In a Thread- ' + Folder1.Text;
ImageEnMView1.FillFromDirectory(Folder1.Text, -1, False, '', True, '',
False, True);
Screen.Cursor := crHourGlass;
try
ImageEnMView1.SelectedImage := 0;
iFilename := ImageEnMView1.ImageFileName[0];
ImageEnView1.IO.LoadFromFile(iFilename);
if Fit1.Checked then
begin
ImageEnView1.AutoFit := True;
ImageEnView1.Fit;
end;
ImageEnView1.Bitmap.Modified := False;
ImageEnMView1.SelectedImage := -1;
finally
Screen.Cursor := crDefault;
end;
end
else
MessageBox(0, PChar('The folder ' + Folder1.Text + ' does not exist.'),
'Warning', MB_ICONWARNING or MB_OK);
end;
procedure TForm1.FillWithThread1Click(Sender: TObject);
{ Fill ImageEnMView with a thread }
begin
if System.SysUtils.DirectoryExists(Folder1.Text) then
begin
ImageEnMView1.Clear;
ImageEnView1.Clear;
Caption := 'Fill ImageEnMView In a Thread- ' + Folder1.Text;
{ Create the thread suspended }
AImageLoadingThread := TImageLoadingThread.Create(True);
{ Set the threads OnTerminate event }
AImageLoadingThread.OnTerminate := ThreadTerminate;
{ Start the thread }
AImageLoadingThread.Start;
{ Processing will occur in the TImageLoadingThread.Execute event }
end;
end;
procedure TForm1.Fit1Click(Sender: TObject);
{ Auto Fit }
begin
ImageEnView1.AutoFit := Fit1.Checked;
if Fit1.Checked then
ImageEnView1.Fit;
end;
procedure TForm1.ThreadTerminate(Sender: TObject);
{ ThreadTerminate }
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;
{ Free the AImageLoadingThread }
AImageLoadingThread := nil;
end;
end. William Miller Adirondack Software & Graphics Email: w2m@frontiernet.net EBook: http://www.imageen.com/ebook/ Apprehend: http://www.frontiernet.net/~w2m/index.html |
cpstevenc |
Posted - Sep 30 2013 : 18:28:41 Kinda revisiting this.
The OnWork event , when I try to populate the ImageENMView, it slows down the UI loading the image up. Being the images for the setup are always on a network drive, its gonna be a bit of delay.
I am using the method of using the ImageENMView.AppendImage(myimage);
If I comment this out, the response in my UI is quick again, and even fixes another UI issue of a drawing problem with another component.
|
cpstevenc |
Posted - Apr 25 2013 : 14:23:31 Sorry for the lack of details.. was on my way out and rushed things.
I am using a combo of both your ideas. I am using the background worker. I THINK locking the paint and then unlocking it fixed my problem. I have yet to repeat. But here is the code I have currently which so far has worked.
procedure Tfrm_FindAndSell_Inventory.ImageThreadWork(Worker: TBackgroundWorker);
var
errormsg: string;
error: Boolean;
t : tImageDataCollection;
x : Integer;
idx : Integer;
begin
try
iowriteln('ImageThread - Work'); // logs to a console that can be open for debugging
Image_Browser.LockPaint; // locks paint
Image_Primary.LockPaint;
image_Browser.Enabled := false; // disable user interactions
Image_Primary.Enabled := false;
Image_Browser.Clear; // clear our image containers
Image_Primary.Clear;
if Assigned(image_Browser) = false then // if somehow the image_Browser got killed exit
Exit;
if Assigned(lastitem) = false then // if our grid record last item is not assigned then exit
Exit;
if lastitem.SearchPart_Data.GUID = '' then // if the GUID is blank then exit
Exit;
FreeAndNil(lastImages);
// Get a List of Images from our given GUID and store to the T object
if cmImage.Get_PartImages(lastitem.SearchPart_Data.GUID, errormsg, t) then
if t.Count > 0 then
begin
lastImages := t;
for x := t.Count - 1 downto 0 do
begin
if worker.CancellationPending then
begin
worker.AcceptCancellation;
Exit;
end;
IDX := Image_Browser.AppendImage;
Image_Browser.ImageID[idx] := X; // ID will store our item index ID in the list
// checks if file exists
if io.newFileExists(t.Item[x].ThumbLocation) then
begin
Image_Browser.ImageFileName[idx] := t.Item[x].Location;
end
else
begin // if did not exist show our Generic Image saying so
Image_Browser.SetImage(idx, Generic_Data.Images.Items[0].bitmap);
end;
Sleep(10); // sleep a momment
end;
end
else
begin
iowriteln('ImageThreadWork Error 1 : ' + ErrorMsg); // we had an error of some sort from the cmImage.Get_PartImages call
end;
if assigned(t) then
begin
idx := t.PrimaryImageIdx; // get an index of our lists "Primary Image"
if idx > -1 then // if it was -1 then don't bother
begin
if io.newFileExists(t.Item[idx].WebLocation) then
image_primary.IO.LoadFromFile(t.Item[idx].WebLocation) // Load image into Image_Primary container
else
Image_primary.Bitmap.Assign(Generic_Data.Images.Items[0].bitmap); // file did not exist so load generic Image saying so
end;
end;
lastImages := t;
except
on e:
Exception do
begin
iowriteln('ImageThreadWork Error 2 : ' + E.Message);
end;
end;
end;
procedure Tfrm_FindAndSell_Inventory.ImageThreadWorkComplete(Worker: TBackgroundWorker;
Cancelled: Boolean);
begin
iowriteln('ImageThread - Complete');
image_Browser.Enabled := true;
Image_Primary.Enabled := true;
Image_Browser.UnLockPaint;
Image_Primary.UnLockPaint;
image_browser.Update;
image_primary.Update;
end;
|
w2m |
Posted - Apr 25 2013 : 13:37:43 I can not debug what you are doing because you do not show any code. quote: I used the background thread deal with great results.
Do you mean that the BackgroundWorker component worked great or did Fabrizio's code work great? If BackgroundWorker component worked "great" and that is causing an exception now, why did it work well before?
I have used the BackGroundWorker component to create thumbnails in a thread using ImageEnIO... but... I guess it is possible the thread in ImageEnMView may be conflicting with the BackgroundWorker component thread.
You could try setting ImageEnMView.EnableImageCaching := False in OnFormCreate which may eliminate a conflict.
Does Fabrizio's method work for you? With Fabrizio's method, ImageEnMView.EnableImageCaching should be set to True.
William Miller Adirondack Software & Graphics Email: w2m@frontiernet.net EBook: http://www.imageen.com/ebook/ Apprehend: http://www.frontiernet.net/~w2m/index.html |
cpstevenc |
Posted - Apr 25 2013 : 12:35:40 I am getting an AV error that I am trying to track down. Using the recommended thread component now..
address 00719A8D
Its in TImageENMView.DrawImage mov eax,[eax+$04]
can't quite figure out why its happening ... its def caused by whats happening in the onwork event but seems to be outside my try/except as i can't catch it.
|
cpstevenc |
Posted - Apr 24 2013 : 20:40:56 I used the background thread deal with great results. I moved the whole database query routine and all into that, and works great. Thanks!
Fabrizio - Ill keep that in mind. |
fab |
Posted - Apr 23 2013 : 23:13:57 You could try also (in a loop, of course...):
ImageEnMView1.ImageFileName[ImageEnMView1.AppendImage()] := 'image1.jpg'; ImageEnMView1.ImageFileName[ImageEnMView1.AppendImage()] := 'image2.jpg'; ..etc...
TImageEnMView1 will load specified images using background threads. You can know when all images to display (all visible images) are loaded handling OnAllDisplayed event.
|
w2m |
Posted - Apr 23 2013 : 11:03:38 I do not know anything in ImageEn that would lock it other than possibly the Enabled property... but you could enable or disable the menu items or buttons that someone could select until you are ready to allow editing.
William Miller |
cpstevenc |
Posted - Apr 23 2013 : 10:48:35 Thanks, checking it out now.
The images are not super big.. ranging from 5k to ~300k .. but because of a design of the system, images are stored on a network share. And the share can be really slow for some people. Some use NetDrive to map drives remotely, which makes it all the slower. And sometimes can have up to 50 + images. So pulling the images over a slow method right now causes lots of delays while its pooling them.
Not my design, someone elses from way back, so I have to deal with it now.
Any recommendations on the work process to make sure while images are being loaded, that someone can't do something in ImageEnMView to make it something ill-wanted to happen?
|
w2m |
Posted - Apr 23 2013 : 09:33:47 The Delphi Area website has a free simple easy to use component called backgroundworker:
http://www.delphiarea.com/products/delphi-components/backgroundworker/
TBackgroundWorker component allows you to run an operation on a separate, dedicated thread. Time-consuming operations like downloads and database transactions can cause your user interface (UI) to seem as though it has stopped responding while they are running. When you want a responsive UI and you are faced with long delays associated with such operations, TBackgroundWorker component provides a convenient solution.
I hsve used this component. It is well written, small and convienent to use. Read the help file to find out how to use it. It is easy...
Quick Usage Summary
Initialize the Background Worker
procedure TForm.Button1Click(Sender: TObject);
begin
// if worker thread is still processing cancel the thread
if BackgroundWorker.IsWorking then
begin
// inform user we are cancelling the last operation
StatusBar.SimpleText := 'Cancelling...';
// cancel the operation
BackgroundWorker.Cancel;
// and wait for worker to stop
BackgroundWorker.WaitFor;
end;
// clear the old thumbnails
ClearThumbnails;
// inform user we are working
StatusBar.SimpleText := 'Creating thumbnails...';
// create new thumbnails
BackgroundWorker.Execute;
end;
The thread is executed here:
procedure TMainForm.BackgroundWorkerWork(Worker: TBackgroundWorker);
// Add your code to fill ImageEnMView here...
begin
end; As far as loading a big image into ImageEnView is quite fast. How big is the image you are loading.... can you post a link to the image here for testing?
William Miller Adirondack Software & Graphics Email: w2m@frontiernet.net EBook: http://www.imageen.com/ebook/ Apprehend: http://www.frontiernet.net/~w2m/index.html |
|
|