Перейти к содержанию
Fire Monkey от А до Я
  • 0

Helper для TBitmap - асинхронная загрузка картинки из URL


Вопрос

Для одного своего проекта сделал, поделюсь, вдруг кому пригодится. Тестировал под Windows и Android.

Для использования просто добавьте BitmapAsyncLoader в uses, а дальше все просто:

ImageControl.Bitmap.LoadFromURLAsync('https://bipbap.ru/wp-content/uploads/2017/10/0_8eb56_842bba74_XL-640x400.jpg');

Код юнита хелпера:

unit BitmapAsyncLoader;

interface

uses
  FMX.Graphics,
  System.Net.HttpClient,
  System.Types,
  System.Classes;

type
  TBitmapAsyncLoader = class helper for TBitmap
    procedure LoadFromUrlAsync(const AUrl : String);
  end;

implementation

var
  AHTTPClient : THTTPClient;

procedure TBitmapAsyncLoader.LoadFromURLAsync(const AURL : String);
begin
  try
    AHTTPClient.BeginGet(
      procedure (const ASyncResult: IAsyncResult)
      var
        AHTTPResponse : IHTTPResponse;
      begin
        if Not ASyncResult.IsCompleted then
          exit;
        try
          AHTTPResponse:=THTTPClient.EndAsyncHTTP(ASyncResult);
        except
        end;
        if Assigned(AHTTPResponse) and (AHTTPResponse.StatusCode = 200) then
          TThread.Synchronize(Nil,
            procedure
            begin
              try
                Self.LoadFromStream(AHTTPResponse.ContentStream);
              except
              end;
            end
          );
      end,
      AURL
    );
  except
  end;
end;

initialization

AHTTPClient:=THTTPClient.Create;

finalization

if Assigned(AHTTPClient) then
begin
  AHTTPClient.DisposeOf;
end;

end.

Архив с тестовым проектом прилагаю.

BitmapAsyncLoader.7z

Ссылка на комментарий

Рекомендуемые сообщения

  • 1

Пока тестировал хелпер в боевом проекте он потихоньку оброс исрпавлениями/улучшениями:

  1. Загрузка из потока сделана через TBitmapSurface - это позволяет избежать множества глюков.
  2. LoadFromStream вынесен из Synchronize (основного потока) в поток HTTPClient - по результатам бенчмарка операция оказалась самая жручая. После исправления интерфейс перестал залипать совсем.
  3. Добавлен overload вариант с передачей в процедуру TListItemImage - для использования в TListView и корректной перерисовки подгруженных картинок через AListItemImage.Invalidate.
unit BitmapAsyncLoader;

interface

uses
  FMX.Graphics,
  FMX.Surfaces,
  System.Net.HttpClient,
  System.Types,
  System.Classes,
  FMX.ListView.Types,
  FMX.ListView.Appearances;

type
  TBitmapAsyncLoader = class helper for TBitmap
    procedure LoadFromURLAsync(const AUrl : String); overload;
    procedure LoadFromURLAsync(const AUrl : String; const AListItemImage : TListItemImage); overload;
  end;

implementation

var
  AHTTPClient : THTTPClient;

procedure TBitmapAsyncLoader.LoadFromURLAsync(const AURL : String);
begin
  try
    AHTTPClient.BeginGet(
      procedure (const ASyncResult: IAsyncResult)
      var
        AHTTPResponse : IHTTPResponse;
        ABitmapSurface : TBitmapSurface;
      begin
        if Not ASyncResult.IsCompleted then
          exit;
        try
          AHTTPResponse:=THTTPClient.EndAsyncHTTP(ASyncResult);
        except
        end;
        if Assigned(AHTTPResponse) and (AHTTPResponse.StatusCode = 200) then
        begin
          ABitmapSurface:=TBitmapSurface.Create;
          if TBitmapCodecManager.LoadFromStream(AHTTPResponse.ContentStream, ABitmapSurface, CanvasClass.GetAttribute(TCanvasAttribute.MaxBitmapSize)) then
            TThread.Synchronize(Nil,
              procedure
              begin
                if Assigned(Self)then
                  Assign(ABitmapSurface);
                ABitmapSurface.Free;
              end
            )
          else
            ABitmapSurface.Free;
        end;
      end,
      AURL
    );
  except
  end;
end;

procedure TBitmapAsyncLoader.LoadFromURLAsync(const AURL : String; const AListItemImage : TListItemImage);
begin
  try
    AHTTPClient.BeginGet(
      procedure (const ASyncResult: IAsyncResult)
      var
        AHTTPResponse : IHTTPResponse;
        ABitmapSurface : TBitmapSurface;
      begin
        if Not ASyncResult.IsCompleted then
          exit;
        try
          AHTTPResponse:=THTTPClient.EndAsyncHTTP(ASyncResult);
        except
        end;
        if Assigned(AHTTPResponse) and (AHTTPResponse.StatusCode = 200) then
        begin
          ABitmapSurface:=TBitmapSurface.Create;
          if TBitmapCodecManager.LoadFromStream(AHTTPResponse.ContentStream, ABitmapSurface, CanvasClass.GetAttribute(TCanvasAttribute.MaxBitmapSize)) then
            TThread.Synchronize(Nil,
              procedure
              begin
                if Assigned(Self) and Assigned(AListItemImage) then
                begin
                  AListItemImage.BeginUpdate;
                  Assign(ABitmapSurface);
                  AListItemImage.Invalidate;
                  AListItemImage.EndUpdate;
                end;
                ABitmapSurface.Free;
              end
            )
          else
            ABitmapSurface.Free;
        end;
      end,
      AURL
    );
  except
  end;
end;

initialization

AHTTPClient:=THTTPClient.Create;

finalization

if Assigned(AHTTPClient) then
  AHTTPClient.DisposeOf;

end.

Тестовый проект, на этот раз с ListView (по кнопке добавляется 100 итемов) прилагаю.

BitmapAsyncLoaderListView.7z

Ссылка на комментарий
  • 1

Окончательный вариант. Долго бился с изменением размера картинки внутри потока (к примеру что бы в ListView не грузить картинки больше чем нужно). Средствами TBitmap это оказалось сделать невозможно (именно в потоке), чтение форумов, issue Эмбаркадеро, привело к туманному выводу что проблема в архитектуре FMX.  В Токио, TBitmap стал потокобезопасным - это означает что никогда не пытайтесь использовать Bitmap в потоке, рано или поздно получите артефакты и глюки.

Я решил проблему отказавшись от работы с TBitmap в потоке, и использовав для этого TBitmapSurface (загрузка из stream, изменение размеров).

Что умеет хелпер:

1. Загрузка картинки в Bitmap и подгонкой размера (размер можно и не менять - не передавайте параметр ASize)

procedure LoadFromURLAsync(const AURL : String; const ASize : TControlSize = nil); overload;

2. Загрузка картинки в TListItemImage ListView. После Окончания загрузки хелпер выполнит AListItemImage.Invalidate в основном потоке приложения для отрисовки картинки.

procedure LoadFromURLAsync(const AURL : String; const AListItemImage : TListItemImage); overload;

3. Создание картинки. Тоже самое что и предыдущие, но можно сэкономить строчку кода ABitmap:=TBitmap.Create ?

constructor CreateFromUrlAsync(const AURL : String; const AListItemImage : TListItemImage = nil);

По картинкам в ListView - можете на свой страх и риск грузить 100500 картинок, но лучше использовать загрузку только для видимой части (+- еще сколько то итемов). Хелпер тупо грузит картинки и не обеспечивает механизм оптимальной загрузки.

Протестировано на Windows и Android.

Ответы на вопросы которые мне задавали:

  1. Почему не создавать отдельный поток для каждой картинки, в нем создавать THTTPClient и делать запрос - пробовал этот вариант, он медленнее на порядок, даже под виндой это чертовски медленно. AHTTPClient.BeginGet и так создает отдельный поток на каждый запрос.
  2. Почему AHTTPClient глобальный для юнита - в хелпере нельзя вводить свои переменные, а создавать AHTTPClient внутри функции не выйдет - он убьется до завершения потока. И текущий вариант быстрее.

Код хелпера и архив с тестовым проектом:

unit BitmapAsyncLoader;

interface

uses
  System.Net.HttpClient,
  System.Net.URLClient,
  System.SysUtils,
  System.Types,
  System.Classes,
  FMX.Graphics,
  FMX.Surfaces,
  FMX.Types,
  FMX.ListView.Types;

type
  TBitmapAsyncLoader = class helper for TBitmap
  private
    function ResizeBitmapSurface(const ABitmapSurface : TBitmapSurface; const AWidth, AHeight : Integer) : TBitmapSurface;
    procedure SynchronizeAssignFromBitmapSurface(const ABitmapSurface : TBitmapSurface; const AListItemImage : TListItemImage = nil);
    procedure StartHTTPThread(const AURL : String; const AWidth, AHeight : Integer; const AListItemImage : TListItemImage = nil);
  public
    procedure LoadFromURLAsync(const AURL : String; const ASize : TControlSize = nil); overload;
    procedure LoadFromURLAsync(const AURL : String; const AListItemImage : TListItemImage); overload;
    constructor CreateFromUrlAsync(const AURL : String; const AListItemImage : TListItemImage = nil);
  end;

implementation

type
  THTTPClientListener = class
    class procedure HTTPClientValidateServerCertificate(const Sender: TObject; const ARequest: TURLRequest; const Certificate: TCertificate; var Accepted: Boolean);
  end;

var
  AHTTPClient : THTTPClient;

constructor TBitmapAsyncLoader.CreateFromUrlAsync(const AURL : String; const AListItemImage : TListItemImage = nil);
begin
  Create;
  LoadFromURLAsync(AURL, AListItemImage);
end;

procedure TBitmapAsyncLoader.LoadFromURLAsync(const AURL : String; const AListItemImage : TListItemImage);
var AWidth, AHeight : Integer;
begin
  if Assigned(AListItemImage) then
  begin
    AWidth:=Round(AListItemImage.Width);
    AHeight:=Round(AListItemImage.Height);
  end
  else
  begin
    AWidth:=-1;
    AHeight:=-1;
  end;
  StartHTTPThread(AURL, AWidth, AHeight, AListItemImage);
end;

procedure TBitmapAsyncLoader.LoadFromURLAsync(const AURL : String; const ASize : TControlSize = nil);
var AWidth, AHeight : Integer;
begin
  if Assigned(ASize) then
  begin
    AWidth:=Round(ASize.Width);
    AHeight:=Round(ASize.Height);
  end
  else
  begin
    AWidth:=-1;
    AHeight:=-1;
  end;
  StartHTTPThread(AURL, AWidth, AHeight);
end;

function TBitmapAsyncLoader.ResizeBitmapSurface(const ABitmapSurface : TBitmapSurface; const AWidth, AHeight : Integer) : TBitmapSurface;
begin
  if (AWidth <> -1) and (AHeight <> -1) then
  begin
    try
      Result:=TBitmapSurface.Create;
      Result.StretchFrom(ABitmapSurface, AWidth, AHeight, ABitmapSurface.PixelFormat);
    finally
      ABitmapSurface.Free;
    end;
  end
  else
    Result:=ABitmapSurface;
end;

procedure TBitmapAsyncLoader.SynchronizeAssignFromBitmapSurface(const ABitmapSurface : TBitmapSurface; const AListItemImage : TListItemImage = nil);
begin
  TThread.Synchronize(Nil,
    procedure
    begin
      Assign(ABitmapSurface);
      ABitmapSurface.Free;
      if Assigned(AListItemImage) then
        AListItemImage.Invalidate;
    end
  );
end;

procedure TBitmapAsyncLoader.StartHTTPThread(const AURL : String; const AWidth, AHeight : Integer; const AListItemImage : TListItemImage = nil);
begin
    AHTTPClient.BeginGet(
      procedure (const ASyncResult: IAsyncResult)
      var
        AHTTPResponse : IHTTPResponse;
        ABitmapSurface : TBitmapSurface;
      begin
        if Not ASyncResult.IsCompleted then
          exit;
        try
          AHTTPResponse:=THTTPClient.EndAsyncHTTP(ASyncResult);
        except
          exit;
        end;
        if Assigned(AHTTPResponse) and (AHTTPResponse.StatusCode = 200) then
        begin
          ABitmapSurface:=TBitmapSurface.Create;
          if TBitmapCodecManager.LoadFromStream(AHTTPResponse.ContentStream, ABitmapSurface, Self.CanvasClass.GetAttribute(TCanvasAttribute.MaxBitmapSize)) then
          begin
            ABitmapSurface:=ResizeBitmapSurface(ABitmapSurface, AWidth, AHeight);
            SynchronizeAssignFromBitmapSurface(ABitmapSurface, AListItemImage);
          end;
        end;
      end,
      AURL
    );
end;

class procedure THTTPClientListener.HTTPClientValidateServerCertificate(const Sender: TObject; const ARequest: TURLRequest; const Certificate: TCertificate; var Accepted: Boolean);
begin
  Accepted:=True;
end;

initialization

AHTTPClient:=THTTPClient.Create;
AHTTPClient.OnValidateServerCertificate:=THTTPClientListener.HTTPClientValidateServerCertificate;

finalization

if Assigned(AHTTPClient) then
  AHTTPClient.DisposeOf;

end.

 

BitmapAsyncLoader.7z

Ссылка на комментарий
  • 0

Доп вопрос в тему: Как вы с fs загружаете картинки?
по тутору андрюши любой доступ к fs желательно поточить, а тут еще и декод... а декод можно параллелить на кучи ядер смартфона
но все поголовно не заморачиваются и тупо LoadFromFile в главном потоке. как правильно? на косяки TBitmap в потоке тоже нарывался прозрачность портит/чернит
1. допустим есть форма и TImage на ней, и в него надо положить logo.png
2. тоже самое но 10-20 images (параллельный декод)
 

Ссылка на комментарий
  • 0

ну в общем воркеры имеются... например TTask и запускаются в пуле...
и можно запускать много тасков и они из общего пула не вылезут и делать к примеру так:
данный код не бест и может ломать пикчу, и поэтому вопрошаю кто как... пользую TBitmap а не Surface что тоже не оптимально, но обратно совместимо с vcl (в дефайн обернуть?)

procedure LoadImageAsync(const Name:string; Callback: TProc<TBitmap>);
begin
  TTask.Run(
    procedure
    var Bitmap:TBitmap;
    begin
      Bitmap:=TBitmap.Create;
      try
        Bitmap.LoadFromFile(ImagePath+Name);
        TThread.Synchronize(nil,
          procedure
          begin
            Callback(Bitmap);
          end);
      finally
        Bitmap.Free;
      end;
    end);
end;
  
procedure TmainForm.FormShow(Sender: TObject);
begin
  LoadImageAsync('logo1.png',
    procedure(Bitmap:TBitmap)
    begin
      Image1.Bitmap.Assign(Bitmap);
      Image1.Invalidate;
    end);
  LoadImageAsync('logo2.png',
    procedure(Bitmap:TBitmap)
    begin
      Image2.Bitmap.Assign(Bitmap);
      Image2.Invalidate;
    end);
  LoadImageAsync('logo3.png',
    procedure(Bitmap:TBitmap)
    begin
      Image3.Bitmap.Assign(Bitmap);
      Image3.Invalidate;
    end);
end;

 

Изменено пользователем Slym
Ссылка на комментарий
  • 0

По результатам проб и тестов - не пытыйтесь использовать мой хелпер на длинных ListView или ListView содержимое которого может неожиданно изменится - нарветесь на AV. Я не нашел простого способа проверить существование самого себя и родителей.

По Bitmap.LoadFromFile(ImagePath+Name) - загляните в исходники Эмбаркадеро, там все делается через TBitmapSurface :

procedure TBitmap.LoadFromFile(const AFileName: string);
var
  Surf: TBitmapSurface;
begin
  TMonitor.Enter(Self);
  try
    Surf := TBitmapSurface.Create;
    try
      if TBitmapCodecManager.LoadFromFile(AFileName, Surf, CanvasClass.GetAttribute(TCanvasAttribute.MaxBitmapSize)) then
        Assign(Surf)
      else
        raise EBitmapLoadingFailed.CreateFMT(SBitmapLoadingFailedNamed, [AFileName]);
    finally
      Surf.Free;
    end;
  finally
    TMonitor.Exit(Self);
  end;
end;

Добавил к хелперу 

    procedure LoadFromFileAsync(const AFilePath : String; const ASize : TControlSize = nil); overload;
    procedure LoadFromFileAsync(const AFilePath : String; const AListItemImage : TListItemImage); overload;
    constructor CreateFromFileAsync(const AFilePath : String; const AListItemImage : TListItemImage = nil); overload;

Сам хелпер и и архив с демо-проектом (загружает 1000 картинок в ListView, параллельно изменяя их размер, картинки в комплекте) :

unit BitmapAsyncLoader;

interface

uses
  System.Net.HttpClient,
  System.Net.URLClient,
  System.SysUtils,
  System.Types,
  System.Classes,
  System.Threading,
  FMX.Graphics,
  FMX.Surfaces,
  FMX.Types,
  FMX.ListView.Types;

type
  TBitmapAsyncLoader = class helper for TBitmap
  private
    function ResizeBitmapSurface(const ABitmapSurface : TBitmapSurface; const AWidth, AHeight : Integer) : TBitmapSurface;
    procedure SynchronizeAssignFromBitmapSurface(const ABitmapSurface : TBitmapSurface; const AListItemImage : TListItemImage = nil);
    procedure StartHTTPThread(const AURL : String; const AWidth, AHeight : Integer; const AListItemImage : TListItemImage = nil);
    procedure StartLoadFromFileThread(const AFilePath : String; const AWidth, AHeight : Integer; const AListItemImage : TListItemImage = nil);
  public
    procedure LoadFromURLAsync(const AURL : String; const ASize : TControlSize = nil); overload;
    procedure LoadFromURLAsync(const AURL : String; const AListItemImage : TListItemImage); overload;
    constructor CreateFromUrlAsync(const AURL : String; const AListItemImage : TListItemImage = nil); overload;

    procedure LoadFromFileAsync(const AFilePath : String; const ASize : TControlSize = nil); overload;
    procedure LoadFromFileAsync(const AFilePath : String; const AListItemImage : TListItemImage); overload;
    constructor CreateFromFileAsync(const AFilePath : String; const AListItemImage : TListItemImage = nil); overload;
  end;

implementation

type
  THTTPClientListener = class
    class procedure HTTPClientValidateServerCertificate(const Sender: TObject; const ARequest: TURLRequest; const Certificate: TCertificate; var Accepted: Boolean);
  end;

var
  AHTTPClient : THTTPClient;

procedure TBitmapAsyncLoader.LoadFromFileAsync(const AFilePath : String; const ASize : TControlSize = nil);
var AWidth, AHeight : Integer;
begin
  if Assigned(ASize) then
  begin
    AWidth:=Round(ASize.Width);
    AHeight:=Round(ASize.Height);
  end
  else
  begin
    AWidth:=-1;
    AHeight:=-1;
  end;
  StartLoadFromFileThread(AFilePath, AWidth, AHeight);
end;

procedure TBitmapAsyncLoader.LoadFromFileAsync(const AFilePath : String; const AListItemImage : TListItemImage);
var AWidth, AHeight : Integer;
begin
  if Assigned(AListItemImage) then
  begin
    AWidth:=Round(AListItemImage.Width);
    AHeight:=Round(AListItemImage.Height);
  end
  else
  begin
    AWidth:=-1;
    AHeight:=-1;
  end;
  StartLoadFromFileThread(AFilePath, AWidth, AHeight, AListItemImage);
end;

constructor TBitmapAsyncLoader.CreateFromFileAsync(const AFilePath : String; const AListItemImage : TListItemImage = nil);
begin
  Create;
  LoadFromFileAsync(AFilePath, AListItemImage);
end;

constructor TBitmapAsyncLoader.CreateFromUrlAsync(const AURL : String; const AListItemImage : TListItemImage = nil);
begin
  Create;
  LoadFromURLAsync(AURL, AListItemImage);
end;

procedure TBitmapAsyncLoader.LoadFromURLAsync(const AURL : String; const AListItemImage : TListItemImage);
var AWidth, AHeight : Integer;
begin
  if Assigned(AListItemImage) then
  begin
    AWidth:=Round(AListItemImage.Width);
    AHeight:=Round(AListItemImage.Height);
  end
  else
  begin
    AWidth:=-1;
    AHeight:=-1;
  end;
  StartHTTPThread(AURL, AWidth, AHeight, AListItemImage);
end;

procedure TBitmapAsyncLoader.LoadFromURLAsync(const AURL : String; const ASize : TControlSize = nil);
var AWidth, AHeight : Integer;
begin
  if Assigned(ASize) then
  begin
    AWidth:=Round(ASize.Width);
    AHeight:=Round(ASize.Height);
  end
  else
  begin
    AWidth:=-1;
    AHeight:=-1;
  end;
  StartHTTPThread(AURL, AWidth, AHeight);
end;

function TBitmapAsyncLoader.ResizeBitmapSurface(const ABitmapSurface : TBitmapSurface; const AWidth, AHeight : Integer) : TBitmapSurface;
begin
  if (AWidth <> -1) and (AHeight <> -1) then
  begin
    try
      Result:=TBitmapSurface.Create;
      Result.StretchFrom(ABitmapSurface, AWidth, AHeight, ABitmapSurface.PixelFormat);
    finally
      ABitmapSurface.Free;
    end;
  end
  else
    Result:=ABitmapSurface;
end;

procedure TBitmapAsyncLoader.SynchronizeAssignFromBitmapSurface(const ABitmapSurface : TBitmapSurface; const AListItemImage : TListItemImage = nil);
begin
  TThread.Synchronize(Nil,
    procedure
    begin
      Assign(ABitmapSurface);
      ABitmapSurface.Free;
      if Assigned(AListItemImage) then
        AListItemImage.Invalidate;
    end
  );
end;

procedure TBitmapAsyncLoader.StartHTTPThread(const AURL : String; const AWidth, AHeight : Integer; const AListItemImage : TListItemImage = nil);
begin
    AHTTPClient.BeginGet(
      procedure (const ASyncResult: IAsyncResult)
      var
        AHTTPResponse : IHTTPResponse;
        ABitmapSurface : TBitmapSurface;
      begin
        if Not ASyncResult.IsCompleted then
          exit;
        try
          AHTTPResponse:=THTTPClient.EndAsyncHTTP(ASyncResult);
        except
          exit;
        end;
        if Assigned(AHTTPResponse) and (AHTTPResponse.StatusCode = 200) then
        begin
          ABitmapSurface:=TBitmapSurface.Create;
          if TBitmapCodecManager.LoadFromStream(AHTTPResponse.ContentStream, ABitmapSurface, CanvasClass.GetAttribute(TCanvasAttribute.MaxBitmapSize)) then
          begin
            ABitmapSurface:=ResizeBitmapSurface(ABitmapSurface, AWidth, AHeight);
            SynchronizeAssignFromBitmapSurface(ABitmapSurface, AListItemImage);
          end;
        end;
      end,
      AURL
    );
end;

procedure TBitmapAsyncLoader.StartLoadFromFileThread(const AFilePath : String; const AWidth, AHeight : Integer; const AListItemImage : TListItemImage = nil);
begin
  TTask.Run(
    procedure
    var
      ABitmapSurface : TBitmapSurface;
    begin
      ABitmapSurface:=TBitmapSurface.Create;
      if TBitmapCodecManager.LoadFromFile(AFilePath, ABitmapSurface, CanvasClass.GetAttribute(TCanvasAttribute.MaxBitmapSize)) then
      begin
        ABitmapSurface:=ResizeBitmapSurface(ABitmapSurface, AWidth, AHeight);
        SynchronizeAssignFromBitmapSurface(ABitmapSurface, AListItemImage);
      end;
    end
  );
end;

class procedure THTTPClientListener.HTTPClientValidateServerCertificate(const Sender: TObject; const ARequest: TURLRequest; const Certificate: TCertificate; var Accepted: Boolean);
begin
  Accepted:=True;
end;

initialization

AHTTPClient:=THTTPClient.Create;
AHTTPClient.OnValidateServerCertificate:=THTTPClientListener.HTTPClientValidateServerCertificate;

finalization

if Assigned(AHTTPClient) then
  AHTTPClient.DisposeOf;

end.

 

BitmapAsyncLoader.7z

Изменено пользователем Евгений Корепов
Ссылка на комментарий
  • 0

аяяй! Совсем разленились try юзать, все на RefCounting надеетесь...
ну-ну... а под вендой мемлики плодятся :) (если файла нет -  Surf куда?)
не-не... переделать! а то студенты накопипастят...
 

procedure TBitmapAsyncLoader.LoadImageAsync(const AFilePath:string; Callback: TProc);
begin
  TTask.Run(
    procedure
    var ABitmapSurface : TBitmapSurface;
    begin
      ABitmapSurface:=TBitmapSurface.Create;
      try
        if TBitmapCodecManager.LoadFromFile(AFilePath, ABitmapSurface, CanvasClass.GetAttribute(TCanvasAttribute.MaxBitmapSize)) and assigned(Callback) then
          TThread.Synchronize(nil, Callback);
      finally
        ABitmapSurface.Free;
      end;
    end);
end;



 

Ссылка на комментарий
  • 0
1 час назад, Slym сказал:

TBitmap стал потокобезопасным - это означает что никогда не пытайтесь использовать Bitmap в потоке, рано или поздно получите артефакты и глюки.

странное заключение.... поясните пожалуйста.... 

Изменено пользователем #WAMACO
Ссылка на комментарий
  • 0
3 часа назад, #WAMACO сказал:

странное заключение.... поясните пожалуйста.... 

Пояснение в сообщении которое вы частично процитировали - читать сначала. Ссылки на issue и темы форумов приводить не буду, все вкладки уже позакрывал. Там что то связанное с Canvas и его реализацией в FMX...

Ссылка на комментарий

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить на вопрос...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...