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

Производительность отрисовки текста на мобильных платформах


IVGSoft

Вопрос

Господа! Пишу компонент для отображения сообщений чата с анимированными смайликами. Собственно он уже почти готов.

Под Windows все работает почти прекрасно. Но столкнулся с одной большой проблемой - скорость прорисовки текста под мобильными платформами. В частности было испытано поведение под Андроид.

Когда в сообщении текста не много,то все работает хорошо, но вот если будет большое сообщение, когда почти весь экран в символах, то начинается жуткое торможение. Анализ исходников TTextLayout показал, что для рендеринга используется GPU. Т.е. каждый символ прорисовывается отдельной битмапкой. В этом ничего нового нет, конечно, ведь любой рендеринг текста в глубине так и работает. Но нативное отображение текста по скорости просто не сравнимо!

Собственно вопрос, может кто-то пробовал вывести текст на Андроиде через нативный Canvas? И возможно ли это вообще? Смикшировать использование кавы FMX и нативной Андроид в одном компоненте?

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

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

  • 0
44 минуты назад, IVGSoft сказал:

... И возможно ли это вообще? Смикшировать использование кавы FMX и нативной Андроид в одном компоненте?

Теоретически возможно всё. Практически, если бы это было возможно сделать без переписывания FMX, то это бы сделали с самого начала.

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

Господа! Пишу компонент для отображения сообщений чата с анимированными смайликами. Собственно он уже почти готов.

Под Windows все работает почти прекрасно. Но столкнулся с одной большой проблемой - скорость прорисовки текста под мобильными платформами. В частности было испытано поведение под Андроид.

Когда в сообщении текста не много,то все работает хорошо, но вот если будет большое сообщение, когда почти весь экран в символах, то начинается жуткое торможение. Анализ исходников TTextLayout показал, что для рендеринга используется GPU. Т.е. каждый символ прорисовывается отдельной битмапкой. В этом ничего нового нет, конечно, ведь любой рендеринг текста в глубине так и работает. Но нативное отображение текста по скорости просто не сравнимо!

Собственно вопрос, может кто-то пробовал вывести текст на Андроиде через нативный Canvas? И возможно ли это вообще? Смикшировать использование кавы FMX и нативной Андроид в одном компоненте?

Приветствую,  а можешь поделиться опытом работы со смайлами?

С клавы эмодзи добавляем в memo а дальше как с ними жить? Имею ввиду отправить другу ,брату ,свату ))

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

Приветствую,  а можешь поделиться опытом работы со смайлами?

С клавы эмодзи добавляем в memo а дальше как с ними жить? Имею ввиду отправить другу ,брату ,свату ))

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

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

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

Понял тебя. Спасибо за инфо. просто все таки мечталось что уже это упаковали в какой-нибудь класс ,компонент

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

Промежуточный итог. Переписал компонент используя нативный канвас для отрисовки. Скорость на Андроиде на порядок выше. Никаких тормозов.

Макс, еще раз большое спасибо за наводку! :)

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

Т.е. вы отрисовываете текст на битмапе через nativeDraw , а затем его отображаете?

Поделились бы кусочком кода. :) Опыт никогда не помешает, для развития Delphi FMX сообщества, - чат многим понадобится. 

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

Да, пожалуйста! :) Не уверен на сколько это поможет сообществу.

Спойлер

 


procedure TIVGChatViewer.Paint;
var
  r : TRectF;
  ss : TCanvasSaveState;
  nCanvas : INativeCanvas;
begin
  inherited;
  if csDesigning in ComponentState then
  begin
    r := ClipRect;
    r.Inflate(-1, -1);
    Canvas.BeginScene;
    Canvas.Stroke.Dash := TStrokeDash.Dash;
    Canvas.Stroke.Color := claBlack;
    Canvas.DrawRect(r, 0, 0, [TCorner.TopLeft, TCorner.TopRight, TCorner.BottomLeft, TCorner.BottomRight], 1);
    Canvas.Font.Size := 16;
    Canvas.Fill.Color := claBlack;
    Canvas.FillText(r, Name, false, 1, [], TTextAlign.Center, TTextAlign.Center);
    Canvas.EndScene;
    Exit;
  end;


  if Assigned(Canvas) then begin
    ss := Canvas.SaveState;
    begin
      fMessagesToShow.Clear;
      nCanvas := Canvas.ToNativeCanvas(TDrawMethod.Native);
      nCanvas.Font.Assign(Canvas.Font);
      nCanvas.NativeDraw(LocalRect, procedure
        var
          i : integer;
          canBreak : boolean;
        begin
          canBreak := false;
          for I := fMessages.Count - 1 downto 0 do
            if IsMessageInViewport(fMessages[i]) then
            begin
              canBreak := true;
              r.Top := fMessages[i].MsgPos.Y - fViewPort;
              r.Bottom := r.Top + fMessages[i].BubbleSize.Height;
              r.Left := fMessages[i].MsgPos.X;
              r.Right := fMessages[i].MsgPos.X + fMessages[i].BubbleSize.Width;
              nCanvas.Fill.Color := fMessages[i].BackColor;
              PaintBubble(r, fMessages[i].IsMy, fMessages[i].Selected, nCanvas);
              if fMessages[i].IsMy then
                r.Left := r.Left + 5
              else
                r.Left := r.Left + 15;
              r.Right := r.Left + fMessages[i].MsgSize.Width;
              r.Bottom := r.Top + fMessages[i].UserNameHeight;
              nCanvas.Font.Assign(fHeaderFont);
              nCanvas.Fill.Color := claWhite;
              nCanvas.FillText(r, fMessages[i].User, false, 1, [], TTextAlign.Leading, TTextAlign.Trailing);
              r.Top := r.Bottom + 5;
              r.Left := r.Left + 5;
              r.Right := r.Left + fMessages[i].MsgSize.Width;
              r.Bottom := r.Top + fMessages[i].MsgSize.Height;
              nCanvas.Font.Style := nCanvas.Font.Style - [TFontStyle.fsBold];
              nCanvas.Fill.Color := fMessages[i].TxtColor;
              fMessages[i].RenderMessage(r, ClipRect, nCanvas);
              fMessagesToShow.Add(fMessages[i]);
            end
            else
              if canBreak then
                Break;
          for i := 0 to fMessagesToShow.Count - 1 do
            PaintSmiles(fMessagesToShow[i], nCanvas, fAniEnabled);
        end);
    end;
    Canvas.RestoreState(ss);
  end;
  fAniEnabled := true;
end;
В 11.06.2018 в 19:16, ENERGY сказал:

Т.е. вы отрисовываете текст на битмапе через nativeDraw , а затем его отображаете?

Нет, сразу через NativeDraw. Оно само на битмапе рисует.

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

Сообщения чата представлены вот таким интерфейсом

Спойлер

fMessages : TList<IChatMessage>;
  
  IChatMessage = interface
  ['{8513A7CE-C16C-44D6-BE4A-B06973F9A969}']
    function GetBackColor: TAlphaColor;
    function GetBubbleSize: TSizeF;
    function GetID: integer;
    function GetIsMy: boolean;
    function GetMsg: string;
    function GetMsgPos: TPointF;
    function GetMsgSize: TSizeF;
    function GetTxtColor: TAlphaColor;
    function GetUser: string;
    function GetUserNameHeight: single;
    function GetFont: TFont;
    function GetMaxSize: TPointF;
    function GetSmiles: TList<ISmile>;
    function GetLinks: TList<IChatURL>;
    function GetHasSmiles: boolean;
    function GetHasURL: boolean;
    function GetSmileProvider: ISmileProvider;
    function GetSelected: boolean;
    procedure SetBackColor(const Value: TAlphaColor);
    procedure SetBubbleSize(const Value: TSizeF);
    procedure SetID(const Value: integer);
    procedure SetIsMy(const Value: boolean);
    procedure SetMsg(const Value: string);
    procedure SetMsgPos(const Value: TPointF);
    procedure SetMsgSize(const Value: TSizeF);
    procedure SetTxtColor(const Value: TAlphaColor);
    procedure SetUser(const Value: string);
    procedure SetUserNameHeight(const Value: single);
    procedure SetFont(const Value: TFont);
    procedure SetMaxSize(const Value: TPointF);
    procedure SetAvgSymWidth(const Value: single);
    procedure SetLineHeight(const Value: single);
    procedure SetSmileProvider(const Value: ISmileProvider);
    procedure SetSelected(const Value : boolean);

    function IsPointInMessage(const aPoint : TPointF): boolean;
    function GetLinkByPoint(const aPoint : TPointF): IChatURL;
    procedure RenderMessage(const aRect, aClipRect : TRectF; const aCanvas : INativeCanvas);

    property Msg : string read GetMsg write SetMsg;
    property User : string read GetUser write SetUser;
    property ID : integer read GetID write SetID;
    property IsMy : boolean read GetIsMy write SetIsMy;
    property BubbleSize : TSizeF read GetBubbleSize write SetBubbleSize;
    property MsgSize : TSizeF read GetMsgSize write SetMsgSize;
    property MsgPos : TPointF read GetMsgPos write SetMsgPos;
    property UserNameHeight : single read GetUserNameHeight write SetUserNameHeight;
    property BackColor : TAlphaColor read GetBackColor write SetBackColor;
    property TxtColor : TAlphaColor read GetTxtColor write SetTxtColor;
    property MaxSize : TPointF read GetMaxSize write SetMaxSize;
    property Font : TFont read GetFont write SetFont;
    property Smiles : TList<ISmile> read GetSmiles;
    property Links : TList<IChatURL> read GetLinks;
    property HasSmiles : boolean read GetHasSmiles;
    property HasURL : boolean read GetHasURL;
    property AvgSymWidth : single write SetAvgSymWidth;
    property LineHeight : single write SetLineHeight;
    property SmileProvider : ISmileProvider read GetSmileProvider write SetSmileProvider;
    property Selected : boolean read GetSelected write SetSelected;
  end;

 

 

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

Не зацикливайтесь сейчас на смайлах, делайте итеративно.
До релиза сойдет вставлять в мемо.текст bb-code или нечто подобное, а уже на канве выводить в этом месте смайл.
Сделайте релиз и потом уже постепенно прикручивайте плюшки типа смайлов в мемо.
Все имхо.

Ссылка на комментарий
  • 0
15 минут назад, Barbanel сказал:

Не зацикливайтесь сейчас на смайлах, делайте итеративно.
До релиза сойдет вставлять в мемо.текст bb-code или нечто подобное, а уже на канве выводить в этом месте смайл.
Сделайте релиз и потом уже постепенно прикручивайте плюшки типа смайлов в мемо.
Все имхо.

Да я не зацикливаюсь. :) Смайлы в чате автоматически конвертируются из мнемоник в графику.

Спойлер

  ISmile = interface
  ['{809B9246-71D8-4DE9-A559-2BB91ABD9CBE}']
    procedure SetSprites(const Value : TBitmap);
    function GetSpritesCount: integer;
    function GetSprites: TBitmap;
    procedure SetSpritesCount(const Value: integer);
    procedure SetWidth(const Value: integer);
    procedure SetHeight(const Value: integer);
    function GetHeight: integer;
    function GetWidth: integer;
    procedure SetX(const Value : single);
    procedure SetY(const Value : single);
    function GetX: single;
    function GetY: single;
    function GetCurrentFrame: TBitmap;
    function GetFrame: TBitmap;
    function GetMnemonic: string;
    procedure SetMnemonic(const Value: string);

    function Clone: ISmile;

    property CurrentFrame : TBitmap read GetCurrentFrame;
    property Frame : TBitmap read GetFrame;
    property Sprites : TBitmap read GetSprites write SetSprites;
    property SpritesCount : integer read GetSpritesCount write SetSpritesCount;
    property Width : integer read GetWidth write SetWidth;
    property Height : integer read GetHeight write SetHeight;
    property Mnemonic : string read GetMnemonic write SetMnemonic;
    //position related to message
    property X : single read GetX write SetX;
    property Y : single read GetY write SetY;
  end;


procedure TChatMessageObj.EstimateSize;
var
  lSymW, mW : single;
  k : integer;
  lMsg : string;
  link : IChatURL;

  procedure EstimateInternal(const aSymWidth : single);
  var
    lTxt, lexeme : string;
    lines : TStringList;
    line : TMessageLine;
    obj : TLineObject;
    x, y, maxw : single;
    maxCol, lc, i, j, sp : integer;
    smile : ISmile;
    lTxtLayout : TTextLayout;
  begin
    lTxtLayout := TTextLayoutManager.DefaultTextLayout.Create(nil);
    lTxtLayout.BeginUpdate;
    lTxtLayout.WordWrap := false;
    lTxtLayout.TopLeft := TPointF.Zero;
    lTxtLayout.Trimming := TTextTrimming.None;
    lTxtLayout.MaxSize := TPointF.Create(65536, 65536);
    lTxtLayout.Font.Assign(fFont);
    lTxtLayout.Font.Size := fFont.Size;
    lTxtLayout.EndUpdate;
    lTxt := lMsg;
    for I := 0 to fLines.Count - 1 do
      fLines[i].Free;
    fLines.Clear;
    maxCol := round(fMaxSize.X / aSymWidth);
    lTxt := WrapMessage(lMsg, maxCol);
    lTxt := StringReplace(lTxt, sLineBreak + sLineBreak, sLineBreak, [rfReplaceAll]);
    lc := 0;
    lines := TStringList.Create;
    lines.Text := lTxt;
    lc := lines.Count;
    y := 0;
    maxw := 0;
    fSmiles.Clear;
    for I := 0 to lc - 1 do
    begin
      x := 0;
      line := TMessageLine.Create;
      line.X := x;
      line.Y := y;
      lTxt := lines[i];
      repeat
        sp := FindFirstSmile(lTxt, smile);
        obj.X := x;
        obj.Y := y;
        if sp <> 0 then
        begin
          lexeme := lTxt;
          Delete(lexeme, sp, Length(lTxt) - sp + 1);
          obj.IsText := true;
          obj.Text := lexeme;
          lTxtLayout.Text := lexeme;
          obj.Width := lTxtLayout.TextRect.Width;
          obj.Height := lTxtLayout.TextRect.Height;
          Delete(lTxt, 1, Length(lexeme));
          Delete(lTxt, 1, smile.Mnemonic.Length);
          line.Width := line.Width + obj.Width;
          if line.Height < fLineHeight then
            line.Height := fLineHeight;
          x := x + obj.Width;
          line.Objects.Add(obj);
          smile.X := x;
          smile.Y := y + 5;
          obj.Smile := nil;
          obj.IsText := false;
          obj.Text := '';
          obj.Smile := smile;
          obj.Width := smile.Width;
          obj.Height := smile.Height;
          obj.X := x;
          line.Width := line.Width + obj.Width;
          if line.Height < smile.Height then
            line.Height := smile.Height;
          line.Objects.Add(obj);
          fSmiles.Add(smile);
        end
        else
        begin
          obj.IsText := true;
          obj.Text := lTxt;
          lTxtLayout.Text := lTxt;
          obj.Width := lTxtLayout.TextRect.Width;
          if line.Height < fLineHeight then
            line.Height := fLineHeight;
          line.Objects.Add(obj);
          line.Width := line.Width + obj.Width;
        end;
        x := x + obj.Width;
        if x > maxw then
          maxw := x;
      until sp = 0;
      fLines.Add(line);
      y := y + line.Height + 5;
    end;
    for I := 0 to fLines.Count - 1 do
      for j := 0 to fLines[i].Objects.Count - 1 do
      begin
        obj := fLines[i].Objects[j];
        if not obj.IsText then
          obj.Smile.Y := obj.Y + fLines[i].Height - obj.Smile.Height + 5;
        fLines[i].Objects[j] := obj;
      end;
    fMsgSize.Width := maxw;
    fMsgSize.Height := y;
    FreeAndNil(lines);
    FreeAndNil(lTxtLayout);
  end;
begin
  if fMsg = '' then Exit;
  //extract links here
  fLinks.Clear;
  lMsg := fMsg;
  k := 0;
  fHaveURL := false;
  repeat
    if IsURL(lMsg) then
    begin
      link := TMessageLink.Create;
      link.MaxWidth := fMaxSize.X;
      link.Font := fFont;
      link.URL := ExtractURL(lMsg, '');
      fLinks.Add(link);
      fHaveURL := true;
    end;
  until not IsURL(lMsg);
  //estimate size without links
  lSymW := fAvgSymWidth;
  repeat
    EstimateInternal(lSymW);
    lSymW := lSymW + 0.75;
  until fMsgSize.Width <= fMaxSize.X;
  //place links
  if fLinks.Count > 0 then
  begin
    mW := 0;
    fMsgSize.Height := fMsgSize.Height + fLineHeight + 5;
    fLinksRect := TRectF.Create(0, fMsgSize.Height, 0, fMsgSize.Height);

    for k := 0 to fLinks.Count - 1 do
    begin
      fLinks[k].Position := TPointF.Create(0, fMsgSize.Height);
      if fLinks[k].Width > mW then
        mW := fLinks[k].Width;
      fMsgSize.Height := fMsgSize.Height + fLinks[k].Height;
      fLinksRect.Bottom := fLinksRect.Bottom + fLinks[k].Height;
    end;
    fLinksRect.Right := mW;
    fMsgSize.Width := Max(mW, fMsgSize.Width);
  end;
end;

function TChatMessageObj.FindFirstSmile(const aStr: string; out aSmile : ISmile): integer;
begin
  Result := 0;
  if Assigned(fSmileProvider) then
    Result := fSmileProvider.ScanForSmile(aStr, aSmile)
  else
    aSmile := nil;
end;

 

 

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

В составе HCL есть пример чата со смайлами, rich текстом, ссылками, картинками, автоматической конвертацией ссылок на картинки или на google maps в preview и т.д.. Канвасы там тоже нативные используются.

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

В составе HCL есть пример чата со смайлами, rich текстом, ссылками, картинками, автоматической конвертацией ссылок на картинки или на google maps в preview и т.д.. Канвасы там тоже нативные используются.

Это все замечательно, но чем оно мне поможет? :) 

Ссылка на комментарий
  • 0
  • Модераторы
6 минут назад, asviridenkov сказал:

Тем, что можно было не изобретать велосипед, а воспользоваться готовым решением.

Так у вас такой же велосипед.

Сейчас он напишет «готовое решение» и тоже поставит ценник. 

Ссылка на комментарий
  • 0
3 часа назад, Равиль Зарипов (ZuBy) сказал:

Так у вас такой же велосипед.

Сейчас он напишет «готовое решение» и тоже поставит ценник. 

Можно и так считать. Разница в том, что одно решение сильно более универсальное, может отображать что угодно и оформление меняется через CSS как угодно, без написания кода.

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

Можно и так считать. Разница в том, что одно решение сильно более универсальное, может отображать что угодно и оформление меняется через CSS как угодно, без написания кода.

Александр, я Вас прекрасно понимаю. Вы вложили много труда в свой продукт и хотите его продвигать. У Вас замечательный продукт, но мне не нужен весь функционал. А платить 340$ лишь за часть функционала я не готов.

Желаю Вам удачи в продвижении Вашего, несомненно, прекрасного и интересного продукта! Но давайте не будем разводить оффтоп.

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

Добрый день сообществу!

Выкладываю на суд первый релизик :)

Для создания паков со смайлами служит небольшая утилитка. Пока она позволяет только импортировать смайлы из гифок.

IVG.Chatting.zip

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

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

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

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

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

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

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

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

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

  • Последние посетители   0 пользователей онлайн

    • Ни одного зарегистрированного пользователя не просматривает данную страницу
×
×
  • Создать...