• 0
IVGSoft

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

Вопросы

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

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

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

33 ответа на этот вопрос

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

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 3

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
2 часа назад, IVGSoft сказал:

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

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

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

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

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

думаю он их вручную отрисовует, не в мемо.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 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

Теперь задача еще интересней. Как сделать мемо со смайлами? :)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 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
9 часов назад, IVGSoft сказал:

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
6 минут назад, asviridenkov сказал:

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

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

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

Поделиться сообщением


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

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

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
10 часов назад, asviridenkov сказал:

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

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

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

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

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

IVG.Chatting.zip

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

Не, ну так не интересно 😩
Самое вкусное - зажал! 😭

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

Исходники не вложил. Я это имел в виду.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
1 час назад, Barbanel сказал:

Исходники не вложил. Я это имел в виду.

Да я понял. :)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Для публикации сообщений создайте учётную запись или авторизуйтесь

Вы должны быть пользователем, чтобы оставить комментарий

Создать учетную запись

Зарегистрируйте новую учётную запись в нашем сообществе. Это очень просто!

Регистрация нового пользователя

Войти

Уже есть аккаунт? Войти в систему.

Войти

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

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