Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Общие вопросы > Непонятно ведет себя TPicture в потоке


Автор: Ne1tr1n0 31.8.2012, 15:46
Добрый день!

Пытаюсь сделать просмотр миниатюр в ListView в виртуальном режиме. Делаю так: сначала получаю список файлов в каталоге, заношу в TStringList с помощью AddObject(sr.Name, TObject(IconIndex)) где IconIndex - индекс иконки файла в моем ImageList'e, полученный с помощью SHGetFileInfo. Далее запускается поток, в котором происходит следующее:
Код

procedure TThumbThread.Execute;
var i: Integer;
    FBmp: TBitmap;
    Pic: TPicture;
    Ratio: Integer;
    tmp: Integer;
    Rect: TRect;

  function Max(const A, B: Integer): Integer; //чтоб не включать модуль Math из-за одной функции
  begin
    if A > B then Result := A else Result := B;
  end;

begin
  inherited;
  for i:=0 to FFileList.Count - 1 do //FFileList - список имён файлов в папке
  begin
    Pic:=TPicture.Create; //создаем объект TPicture
    FBmp:=TBitmap.Create;
    FBmp.Assign(BmpCanvas); //подготавливаем битмап к рисованию и добавлению в ImageList (BmpCanvas - заранее созданный битмап с нужным размером и выставленной прозрачностью
    try
      Pic.LoadFromFile(FPath + FFileList[i]); //пытаемся загрузить картинку. Причем если загружен черный или белый квадрат ошибки все равно не происходит.
      Ratio:=Max(Pic.Width div ThumbSize, Pic.Height div ThumbSize);
      With Rect do //подгоняем под размеры тумбы (Thumb)
      begin
        tmp:=Pic.Width div Ratio;
        Left:=(ThumbSize - tmp) div 2;
        Right:=Left + tmp;
        tmp:=Pic.Height div Ratio;
        Top:=(ThumbSize - tmp) div 2;
        Bottom:=Top + tmp;
      end;
      FBmp.Canvas.StretchDraw(Rect, Pic.Graphic); //рисуем уменьшенную копию
      FFIleList.Objects[i]:=TObject(FImageList.Add(FBmp, nil)); //добавляем тумбу в ImageList и подменяем у текущего файла индекс иконки на только что индекс только что добавленной тумбы
    finally
      Pic.Free;
      FBmp.Free; //убираем за собой
    end;
  end;
end;


Причем эта ошибка возникает нерегулярно, на одной и той же картинке может нормально отработать, а может и выдать черный или белый квадрат. Если надо - могу полностью скинуть проект.
Заранее спасибо.

Автор: Dapo 31.8.2012, 17:38
Кто такие BmpCanvas, FFileList, FImageList? F каком потоке они создаются? Почему Вы пилите сук на котором сидите: FBmp.Free; //убираем за собой

Автор: Ne1tr1n0 31.8.2012, 18:22
BmpCanvas - заранее созданный битмап с нужным размером и выставленной прозрачностью. Создан в основном потоке программы (Фактически в FormCreate он создается).
FFileList и FImageList - свойства потомка от TThread (это который TThumbThread). Им при создании потока присваиваются ссылки на соответствующие поля формы.
Код

type
  TThumbThread = class(TThread)
  private
    FPath: string;
    FFileList: TStringList;
    FImgList: TImageList;
  protected
    procedure Execute; override;
  public
    property Path: string write FPath;
    property ImageList: TImageList write FImgList;
    property Files: TStringList write FFileList;
  end;
...
//заполнение списка slFiles. В массиве arrExts содержатся допустимые разрешения файлов. На основании этого же массива заполняется иконками ImageList в FormCreate
  if FindFirst(Path + '*.*', faAnyFile - faDirectory, sr) = 0 then
  repeat
    for i:=Low(arrExts) to High(arrExts) do
      if LowerCase(ExtractFileExt(sr.Name)) = arrExts[i] then
        slFiles.AddObject(sr.Name, TObject(i));
  until FindNext(sr) > 0;
  FindClose(sr);

  ListView1.Items.Count:=slFiles.Count;
  ListView1.Invalidate;

  if slFiles.Count > 0 then
  begin
    NewThumbThread:=TThumbThread.Create(True);
    NewThumbThread.Path:=Path; //путь к текущей папке
    NewThumbThread.Files:=slFiles; //список файлов (TStringList), заполняется в основном потоке программы
    NewThumbThread.ImageList:=ImageList1; //ImageList, привязанный к ListView'у. Частично заполняется иконками файлов в основном потоке программы, и в дальнейшем уже заполняется из потока.
    NewThumbThread.Priority:=tpNormal;
    NewThumbThread.FreeOnTerminate:=False;
    NewThumbThread.Resume;
  end;

Цитата(Dapo @  31.8.2012,  15:38 Найти цитируемый пост)
Почему Вы пилите сук на котором сидите: FBmp.Free; //убираем за собой 

Этот момент не совсем понял. Изображение ведь уже в ImageList'e, зачем оно дальше нужно?

Автор: Illusion Dolphin 31.8.2012, 20:33
Ошибки тут:
Код

FBmp.Assign(BmpCanvas);

и тут:
Код

FBmp.Canvas.StretchDraw(Rect, Pic.Graphic); 

С объектом Canvas работать из потоков нельзя, только через синхронизацию.

Автор: Ne1tr1n0 2.9.2012, 01:41
Вынес работу с Canvas в процедуру синхронизации. Но появились тормоза при прокрутке ListView например. Да и вообще интерфейс стал менее отзывчивым на действия пользователя.
Вот код:
Код

//несколько изменил объявление класса потока
  TThumbThread = class(TThread)
  private
    FPic: TPicture;
    FThumb: TBitmap;
    FSyncIndex: Integer;
...
  protected
    procedure Sync;
...
procedure TThumbThread.Execute;
var i: Integer;
begin
  inherited;
  for i:=0 to FFileList.Count - 1 do
  begin
    FPic:=TPicture.Create;
    try
      FPic.LoadFromFile(FPath + FFileList[i]);
      FSyncIndex:=i;
      Synchronize(Sync);
    finally
      FPic.Free;
    end;
  end;
end;

procedure TThumbThread.Sync;
var b1: TBitmap;

  procedure CreateThumb(bmp: TBitmap; Size: Integer);
  begin
    bmp.SetSize(Size, Size);
    bmp.Canvas.StretchDraw(bmp.Canvas.ClipRect, b1);
  end;

begin
  b1:=TBitmap.Create;
  b1.Assign(FPic.Graphic);
  FThumb:=TBitmap.Create;
  FThumb.Assign(BmpCanvas);
  CreateThumb(FThumb, ThumbSize);
  FFIleList.Objects[FSyncIndex]:=TObject(FImgList.Add(FThumb, nil));
  FThumb.Free;
  b1.Free;
end;
Если же адаптировать код из этого поста: http://forum.vingrad.ru/forum/topic-152623.html (второй вариант), то наблюдается та же картина что и у меня была. 
Как теперь можно сделать?

Добавлено через 3 минуты и 19 секунд
ЗЫ: Я так полагаю основные тормоза создает StretchDraw. Возможно с ним что-нить можно сделать?

Автор: Illusion Dolphin 2.9.2012, 12:01
Каждый из этих методов создаёт тормоза.
Assign одного TGraphic к другому везде реализован по-разному, в зависимостиот этого это можно выполнять в потоке или надо писать ручной Assign, который не будет затрагивать Canvas. Это зависит от конкретных типов изображений. 
StretchDraw - тут уже полегче (если не заморачиваться с качеством), есть готовые решения типа http://forum.vingrad.ru/forum/topic-49118/hl/resize/index.html, они не работают с канвой, поэтому это можно делать в потоке без синхронизации.

Автор: Ne1tr1n0 2.9.2012, 13:58
Поискал ещё насчет канвы и потоков - действительно пишут, что да, класс не Thread-safe, так что могут быть проблемы. Некоторые рекомендуют использовать методы Canvas.Lock и Canvas.Unlock соответственно сразу после того как создали битмап и закончили с ним работать. Вроде как это специальные методы Borland/Embarcadero (не знаю точно когда они появились), позволяющие работать с канвой в многопоточных приложениях. Попробовал у себя использовать - не помогает, хотя ошибок стало заметно меньше. Может быть стоит покопать поглубже в сторону этих Lock/Unlock? Есть смысл?
Вот код с блокированием канвы:
Код

procedure TThumbThread.Execute;
var i: Integer;
    Bmp: TBitmap;
    Pic: TPicture;
    Rect: TRect;
begin
  inherited;
  for i:=0 to FFileList.Count - 1 do
  begin
    Pic:=TPicture.Create;
    Bmp:=TBitmap.Create;
//    Pic.Bitmap.Canvas.Lock;
    Bmp.Canvas.Lock;
    Bmp.Assign(BmpCanvas);
    try
      Pic.LoadFromFile(FPath + FFileList[i]);
      if Pic.Width > Pic.Height then
      begin
        Rect.Right := ThumbSize;
        Rect.Bottom := (ThumbSize * Pic.Height) div Pic.Width;
      end
      else
      begin
        Rect.Bottom := ThumbSize;
        Rect.Right := (ThumbSize * Pic.Width) div Pic.Height;
      end;

      Rect.Left:=(ThumbSize - Rect.Right) div 2;
      Rect.Top:=(ThumbSize - Rect.Bottom) div 2;
      Rect.Right:=Rect.Left + Rect.Right;
      Rect.Bottom:=Rect.Top + Rect.Bottom;

      Bmp.Canvas.StretchDraw(Rect, Pic.Graphic);
      FFileList.Objects[i]:=TObject(FImgList.Add(Bmp, nil));
    finally
//      Pic.Bitmap.Canvas.Unlock;
      Bmp.Canvas.Unlock;
      Pic.Free;
      Bmp.Free;
    end;
  end;
end;


Насчет ресайза без использования канвы - спасибо, тоже посмотрю сейчас

Автор: MetalFan 2.9.2012, 14:26
Я тоже сталкивался с проблемами при работе с TBitmap/TPicture в потоке... На сколько я помню, TBitmap'у не удавалось порой выделять память при использовании его в отдельном потоке. При чем при работе в осн.потоке проблем не возникало.
Как решил проблему уже не помню, возможно свел вероятность ее возникновение к минимуму.

Автор: Illusion Dolphin 2.9.2012, 20:32
Код

Bmp.Assign(BmpCanvas);

Этот код блокирует все другие операции TBitmap.Assign ра время работы во всех потоках (инфа: исходники VCL)
Цитата

Некоторые рекомендуют использовать методы Canvas.Lock и Canvas.Unlock

Возможно это решит часть проблем, но я это обходил через неиспользование TCanvas в потоках. 

Автор: Ne1tr1n0 3.9.2012, 11:58
Что-то все равно проскакивают ошибки. Может я что не так делаю?
Код

      Pic.LoadFromFile(FPath + FFileList[i]);
      Bmp.Assign(Pic.Graphic);
      SmoothResize(Bmp, Thumb);
      FFileList.Objects[i]:=TObject(FImgList.Add(Thumb, nil));

Автор: MetalFan 3.9.2012, 13:42
Ne1tr1n0, А FImgList - эт случайно не ссылка на компонент на форме? может лучше это (добавление картинки в ImageList) делать в основном потоке через Synchronize?

Автор: Ne1tr1n0 3.9.2012, 13:45
Так точно, она и есть. Это ImageList, привязанный к ListView'у.
Сейчас попробую вынессти добавление в ImageList в Syncronize.

Автор: Ne1tr1n0 3.9.2012, 15:59
Да, так работает. И вроде даже не тормозит. Я тут просто решил поизвращаться, создал класс TItemData, в нем одним из полей TMemoryStream, так вот, после SmoothResize я запихивал тумбу в TJPEGImage, сжимал её, и сохранял в этот TMemoryStream. И добавлял каждый экземпляр класса в качестве связанного объекта в список файлов. А в ListView.OnGetImageIndex уже доставал из потока, преобразовывал в битмап и добавлял его в ImageList, назначая индекс только что добавленного элемента каждому ListItem'у. Тоже работало, но вариант MetalFan'a мне как-то поизящней кажется. Пожалуй тему можно закрывать, всем спасибо за обсуждение smile

Автор: MetalFan 3.9.2012, 20:47
Цитата(Ne1tr1n0 @  3.9.2012,  15:59 Найти цитируемый пост)
я запихивал тумбу в TJPEGImage, сжимал её, и сохранял в этот TMemoryStream.

Ватэто изврат) Типа память экономил?

А проблема скорее всего была в вызове метода TImageList.Add из потока... что вызывало скорее всего Update и выполнение кучи VCL-ного кода в доп.потоке вместо основного... Не зря ж везде советуют НЕ использовать VCL компоненты в доп. потоках без синхронизации или без понимания внутреннего устройства.

Автор: Ne1tr1n0 3.9.2012, 22:17
Ну можно и так сказать))) Только все равно тумба потом в ImageList'e оказывалась в виде битмапа, так что толку от такой экономии не особо было. Там просто одна из мыслей была ещё в OnCustomDrawItem самому её отрисовывать, но отказался от этой затеи.
И в каком-то примере видел подобную штуку, но там свой контрол был, внешне напоминающий ListView, но с собственным механизмом отрисовки, там как раз из жпега всё рисовалось.
Тут ещё попутно вопрос возник. Не совсем правда к этой теме относящийся, так что может лучше и в отдельную ветку вынести.
Вот делаю я значит в процедуре синхронизации такую штуку:
Код

procedure TThumbThread.Sync;
var Thumb: TBitmap;
begin
  if Terminated then Exit;

  Thumb:=TBitmap.Create;
  Thumb.Assign(BmpCanvas);
  Thumb.Canvas.Draw((ThumbSize - FBmp.Width) div 2, (ThumbSize - FBmp.Height) div 2, FBmp);
  FFileList.Objects[FSyncIndex]:=TObject(FImgList.Add(Thumb, nil));
  FBmp.Free;
  Thumb.Free;
end;
FBmp - поле класса потока, в него я с помощью SmoothResize заношу пропорционально уменьшенную картинку. Но тумбы-то квадратные, следовательно здесь я выравниваю уменьшенную копию изображения посередине тумбы (с помощью Canvas.Draw и там нужные координаты задаю). BmpCanvas - это подготовленный прозрачный пустой "холст", я на нем с помощью DrawIcon вывожу иконки файлов, которые отображаются пока не сгенерилась тумба для файла. Так вот после DrawIcon изображения с иконками остаются прозрачными, это хорошо заметно при выделении итема в ListView. 
user posted image 
А после приведенного вызова Canvas.Draw видимо из-за того, что для FBmp в SmoothResize явно задается PixelFormat:=pf24bit у меня прозрачность теряется. И никакими TransparentColor/TransparentMode или PixelFormat:=pf32bit вернуть её не удается. В результате в ListView, если выделить элемент, то вокруг изображения образуется белый фон (вместо синего выделения). Выглядит это примерно так (у выделенного итема сверху и снизу относительно широкие белые полосы): 
user posted image
Вот как бы от этого ещё избавиться? Спасибо.

Автор: Ne1tr1n0 4.9.2012, 18:05
Вроде разобрался. Создал маску, теперь с ней добавляю в ImageList

Автор: Nialon 5.10.2012, 21:31
Цитата(Ne1tr1n0 @  31.8.2012,  15:46 Найти цитируемый пост)
Причем эта ошибка возникает нерегулярно, на одной и той же картинке может нормально отработать, а может и выдать черный или белый квадрат. Если надо - могу полностью скинуть проект.


Прочитал шапку, уже все понял. Перед финальным кодом надо обязательно синхронизировать !весь! потоковый блок.
Особенно если не разбираетесь или не читали литературу где написано что любое обращение к VCL надо синхронизировать.

Как только все идет по маслу, начинаете укорачивать блоки синхронизации до не безопасных строк. и все.
У меня точно такой же код с загрузкой, программа периодически сыпалась, что есть самый 1 признак, на LoadFromFile и все в таком духе.

К сожалению, с ListView работал в другом режиме, догадываюсь зачем DrawIcon, там вроде список из иконок,
но без понятия куда, точнее для чего, вы отрисоваете BmpCanvas, как и что .... 

DrawIcon специально создана для иконок и она учитывает их прозрачность, рисуя на холсте.
Даже DrawIcon, если не изменяет память, тоже фигово работает с иконками. Лучше брать LoadImage из unit Windows.
В Canvas нет умных методов, они простые. Если хотите прозрачность используйте что-то другое.

Суть вроде в том что сам TBitmap не поддерживает прозрачность. У вас белая кайма вокруг картинки, уверены что это не холст?
Вы нарисовали поверх нее !узкую! картинку. Если да, сузьте bitmap через setsize и рисуйте поверх.
Мой вариант на данный момент закрашивать хослт под цвет кнопок, чтобы он сливался.

Вообще я сейчас сам работаю над проектом где рисую миниатюры на кнопках, там везде проглядывают бока при выделении.
Я еще с этим не ковырялся, потом займусь.

----------------------------------------------------------

Упс, только заметил последнее сообщение. Видимо было на другой странице.
Ну конечно, маска. Добавляете в ImageList. Я тоже сохраняю все туда.
Можете привести кусок кода, который делает маску из финального изображения? Как она делается?
Чуствую, придется читать литературу по этой теме.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)