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

Ошибка "Bitmap size too big"


Евгений Корепов

Вопрос

На незначительном количестве устройств, менее 0,1%, получаю ошибку "Bitmap size too big" при AImage.Bitmap.LoadFromStream(AMemoryStream). Подозреваю что ошибка происходит на слабых устройствах. Картинка 250х250 png. Код выполняется в основном потоке (в интернетах были упоминания что глючит эта операция в отдельном потоке на каких то версиях Delphi).

Как предотвратить подобное? Можно как то определить максимальный размер картинки для текущего устройства? Или может дело не в свободной памяти, а в чем то еще?

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

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

  • 0
  • Администраторы
  • 0

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

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

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

Причин несколько:

  1. Ява сама не может отобразить большие изображения так же. Но там причина ограничение по потребляемой памяти. Поэтому если открыть документацию, то можно увидеть специальные методы и рекомендации для сжатия размера для отображения на экран. Вместо того, что выводить картинку в 10 МгП на экран, апи выдает картинку 64 на 64 или тд.
  2. В FireMonkey есть два типа для работы с изображениями. 
    1. TBitmap - построен на использовании текстур. Отсюда и идет ограничение на размер в зависимости от аппаратной части девайса.
    2. TBitmapSurface - не зависит ни от чего, кроме, как от памяти. Этот тип полностью совместим с TBitmap - это значит, что можно копировать изображения друг в друга. Это тип создан для хранения, как раз больших изображений.
Ссылка на комментарий
  • 0
В 3/29/2016 в 11:55, Brovin Yaroslav сказал:

На тестовом устройстве с помощью кода

Var MaxWidthHeight: Integer;
begin
  MaxWidthHeight := TCanvasManager.DefaultCanvas.GetAttribute(TCanvasAttribute.MaxBitmapSize);

получаю значение 4096. В каких попугаях это число? Судя по MaxWidthHeight это высота изображения? Как получить тогда ширину? Или его размер в байтах? Хотя это врядли, на этом устройстве успешно загружаются картинки в 200 килобайт.

 

В 3/29/2016 в 13:29, Brovin Yaroslav сказал:

Причин несколько:

  1. Ява сама не может отобразить большие изображения так же. Но там причина ограничение по потребляемой памяти. Поэтому если открыть документацию, то можно увидеть специальные методы и рекомендации для сжатия размера для отображения на экран. Вместо того, что выводить картинку в 10 МгП на экран, апи выдает картинку 64 на 64 или тд.
  2. В FireMonkey есть два типа для работы с изображениями. 
    1. TBitmap - построен на использовании текстур. Отсюда и идет ограничение на размер в зависимости от аппаратной части девайса.
    2. TBitmapSurface - не зависит ни от чего, кроме, как от памяти. Этот тип полностью совместим с TBitmap - это значит, что можно копировать изображения друг в друга. Это тип создан для хранения, как раз больших изображений.

Подскажите наиболее правильный путь в моей ситуации - на форме лежит TImage и нужно загрузить в него картинку:

  1. Проверять максимальный размер картинки, если загрузить не удастся, то отлупливать пользователя фразой "Нищебродам вход воспрещен".
  2. Переписать класс TImage на использование TBitmapSurface вместо TBitmap
  3. Загружать картинку сначала в TBitmapSurface, изменять размер и копировать в визуальный Image.Bitmap. Вот тут облом - если не ошибаюсь TBitmapSurface не умеет абсолютно ничего, никаких  LoadFromStream (как в него вообще загрузить внешнее изображение?) только readonly свойства и методы.
Изменено пользователем Евгений Корепов
Ссылка на комментарий
  • 0
  • Модераторы

скорей всего 4096x4096 это размер в пикселях

а в битах думаю вот так 4096x4096x24/32(смотря какая глубина цвета используется)

Изменено пользователем ZuBy
Ссылка на комментарий
  • 0
  • Администраторы
В 29.03.2016 в 17:27, Евгений Корепов сказал:

Подскажите наиболее правильный путь в моей ситуации - на форме лежит TImage и нужно загрузить в него картинку:

  1. Проверять максимальный размер картинки, если загрузить не удастся, то отлупливать пользователя фразой "Нищебродам вход воспрещен".
  2. Переписать класс TImage на использование TBitmapSurface вместо TBitmap
  3. Загружать картинку сначала в TBitmapSurface, изменять размер и копировать в визуальный Image.Bitmap. Вот тут облом - если не ошибаюсь TBitmapSurface не умеет абсолютно ничего, никаких  LoadFromStream (как в него вообще загрузить внешнее изображение?) только readonly свойства и методы.
  1. Нет. Это значит, что не нужно загружать 4 Мб фотографию в TImage с целью отобразить ее в итоге в размере 400х200.
  2. Нет, просто присваивайте в Bitmap TBtmapSurface. Bitmap.Assign(BitmapSurface)
  3. Методы по загрузки из файлов и потоков есть. Смотрите TBitmapCodecManager
Ссылка на комментарий
  • 0
6 часов назад, Brovin Yaroslav сказал:
  1. Нет. Это значит, что не нужно загружать 4 Мб фотографию в TImage с целью отобразить ее в итоге в размере 400х200.
  2. Нет, просто присваивайте в Bitmap TBtmapSurface. Bitmap.Assign(BitmapSurface)
  3. Методы по загрузки из файлов и потоков есть. Смотрите TBitmapCodecManager

Огромное вам спасибо! Стало много понятнее. Повторюсь: картинки 250х250 png. Сделал процедуру:

Uses FMX.Graphics, FMX.Surfaces, FMX.Types;


function GetMaxImageSize : Integer;
begin
  Result:=TCanvasManager.DefaultCanvas.GetAttribute(TCanvasAttribute.MaxBitmapSize);
end;

procedure CheckAndLoadFromStream(const AStream : TStream; const ABitmap : TBitmap);
Var MaxImageSize : Integer;
    ABitmapSurface : TBitmapSurface;
begin
  ABitmapSurface:=TBitmapSurface.Create;
  AStream.Position:=0;
  TBitmapCodecManager.LoadFromStream(AStream,ABitmapSurface);
  MaxImageSize:=GetMaxImageSize;
//  MaxImageSize:=64;
  if ABitmapSurface.Height>MaxImageSize then
    ABitmapSurface.SetSize(MaxImageSize,MaxImageSize,ABitmapSurface.PixelFormat);
  if Assigned(ABitmap) then
    ABitmap.Assign(ABitmapSurface);
  ABitmapSurface.Free;
end;

Все отлично работает, до момента раскоментирования строчки   "MaxImageSize:=64" (имитация слабого устройства) - процедура ABitmapSurface.SetSize устанавливает новый размер, но изображение при этом теряется, на форме, в TImage пустота. Видимо я что то не так делаю :-( Перебрал все возможные ABitmapSurface.PixelFormat - результата нет.

Следующий код

  ABitmapSurface:=TBitmapSurface.Create;
  TBitmapCodecManager.LoadFromFile('d:\source.png',ABitmapSurface);
  MaxImageSize:=250;
  ABitmapSurface.SetSize(MaxImageSize,MaxImageSize);
  TBitmapCodecManager.SaveToFile('d:\source_resize.png', ABitmapSurface);

Из файла source.png размером 79642 байта делает файл source_resize.png размером 351 байт. Изображение исчезает.

Что я делаю не так? Заранее благодарю за ответ!

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

Сам себе отвечаю, процедура procedure TBitmapSurface.SetSize(const AWidth, AHeight: Integer; const APixelFormat: TPixelFormat), судя по коду:

procedure TBitmapSurface.SetSize(const AWidth, AHeight: Integer; const APixelFormat: TPixelFormat);
var  NumOfBytes: Integer;
begin
  FPixelFormat := APixelFormat;
  if FPixelFormat = TPixelFormat.None then
    FPixelFormat := TPixelFormat.BGRA;

  FBytesPerPixel := PixelFormatBytes[FPixelFormat];

  FWidth := Max(AWidth, 0);
  FHeight := Max(AHeight, 0);
  FPitch := FWidth * FBytesPerPixel;
  NumOfBytes := FWidth * FHeight * FBytesPerPixel;

  ReallocMem(FBits, NumOfBytes);
  FillChar(FBits^, NumOfBytes, 0);
end;

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

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

Ура! Заработало с TBitmapSurface.StretchFrom :

  ABitmapSurface:=TBitmapSurface.Create;
  ABitmapSurfaceResize:=TBitmapSurface.Create;
  TBitmapCodecManager.LoadFromFile('d:\source.png',ABitmapSurface); // png 250x250
  MaxImageSize:=50;
  ABitmapSurfaceResize.StretchFrom(ABitmapSurface,MaxImageSize,MaxImageSize);
  TBitmapCodecManager.SaveToFile('d:\source_resize.png', ABitmapSurfaceResize);

Все работает отменно :-)

Итоговый код для загрузки картинки из потока и устранения ошибки "Bitmap size too big":

Uses FMX.Graphics,  FMX.Surfaces;


function GetMaxImageSize : Integer;
begin
  Result:=TCanvasManager.DefaultCanvas.GetAttribute(TCanvasAttribute.MaxBitmapSize);
end;

procedure CheckAndLoadFromStream(const AStream : TStream; const ABitmap : TBitmap);
Var MaxImageSize : Integer;
    ABitmapSurface,ABitmapSurfaceResize : TBitmapSurface;
begin
  ABitmapSurface:=TBitmapSurface.Create;
  AStream.Position:=0;
  TBitmapCodecManager.LoadFromStream(AStream,ABitmapSurface);
  MaxImageSize:=GetMaxImageSize;
  if ABitmapSurface.Height>MaxImageSize then
  begin
    ABitmapSurfaceResize:=TBitmapSurface.Create;
    ABitmapSurfaceResize.StretchFrom(ABitmapSurface,MaxImageSize,MaxImageSize);
    if Assigned(ABitmap) then
      ABitmap.Assign(ABitmapSurfaceResize);
    ABitmapSurfaceResize.Free;
  end
  Else
    if Assigned(ABitmap) then
      ABitmap.Assign(ABitmapSurface);
  ABitmapSurface.Free;
end;

Всем спасибо за помощь! Особая благодарность Ярославу! 

P.S. Процедура для частного случая с квадратным изображением. Для меня осталось не до конца ясным значение возвращаемое TCanvasManager.DefaultCanvas.GetAttribute(TCanvasAttribute.MaxBitmapSize), исходил из догадки что это максимальный размер изображения по вертикали в пикселях. Может Ярослав поставит все точки и разъяснит этот вопрос?

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

P.S. Процедура для частного случая с квадратным изображением. Для меня осталось не до конца ясным значение возвращаемое TCanvasManager.DefaultCanvas.GetAttribute(TCanvasAttribute.MaxBitmapSize), исходил из догадки что это максимальный размер изображения по вертикали в пикселях. Может Ярослав поставит все точки и разъяснит этот вопрос?

Если я правильно помню, то это сторона квадрата в пикселях. Если возвращает 1000, то это значит, что допустимо хранение изображение размером до 1000 х 1000. На самом деле TBitmap в своей основе использует текстуру для хранения картинки. Поэтому этот размер и ограничение зависит от графического процессора.

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

К сожалению я рано радовался. Три недели тестов показали что ошибка "Bitmap size too big" продолжает появляться у не значительной части пользователей.

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

 end
  Else
    if Assigned(ABitmap) then
      ABitmap.Assign(ABitmapSurface); // Вот здесь ошибка

Продолжаю наблюдение...

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

Да ошибка подтвердилась. MaxImageSize=4096, но  ABitmap.Assign(ABitmapSurface) выдает ошибку "Bitmap size too big" при загрузке картинки 250x250

Больше идей у меня нет...

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

Вот и у меня вылезла эта ошибка ."Bitmap size too big". Причем ошибка коварная, появляется не всегда и из-за этого непонятно почему и как ее отловить.

BigBitmap.png

 

Подозреваю что проблема происходит здесь (к ListView прикреплен ImageList с 2 картинками 44х44):

var
  k:integer;
  Al:TAl;
  IItem:TListItemImage;
  TItem:TListItemText;
begin
      Lsv.BeginUpdate;
      for k := 0 to Lsv.ItemCount-1 do
      begin        
        IItem:=Lsv.Items[k].Objects.FindDrawable('Imp1') as TListItemImage;
        TItem:=Lsv.Items[k].Objects.FindDrawable('TxtNumber') as TListItemText;

        Al:=GetAlFromList(Lsv.Items[k].Tag);
        if Al.Tip=0 then
        begin                                    
          if IItem<>nil then IItem.ImageIndex:=0; 
          if TItem<>nil then TItem.TextColor:=$FFFF0000;
        end
        else
        begin
          if IItem<>nil then IItem.ImageIndex:=1; 
          if TItem<>nil then TItem.TextColor:=$FF0000FE;
        end
      end;
      Lsv.EndUpdate;
end;

Продолжаю выяснять откуда ноги растут.

 

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

Сделаю предположение что ВОЗМОЖНО данная ошибка вылезает при невозможности выделить память под очередную текстуру

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

скажите, кто-нибудь разобрался с проблемой?

у меня все тоже - 

 ABitmap.Assign(ABitmapSurfaceResize);

даже ПОСЛЕ преобразования размеров выдает ошибку  Bitmap size too big

в FMX ужасно раздражает что на корректную работу строчки TBitmap().LoadFromFile(filename) можно потратить больше суток, причем не факт что в новой версии все будет работать стабильно

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

Я смирился. У меня в приложении с ~20000 пользователями ошибка вылезает у 290 :-( Т.е. это примерно 1.5%. 

Изменено пользователем Евгений Корепов
Ссылка на комментарий
  • 0
В 31.03.2016 в 15:53, Brovin Yaroslav сказал:

Если я правильно помню, то это сторона квадрата в пикселях. Если возвращает 1000, то это значит, что допустимо хранение изображение размером до 1000 х 1000

Опытным путем выяснил что это не так. Это максимальная сторона изображения, а пропорции его не должны превышать пропорции монитора (может десктопа?).

Т.е. если TCanvasManager.DefaultCanvas.GetAttribute(TCanvasAttribute.MaxBitmapSize) возвращает 8192, то для монитора с разрешением 1440х900 можно создать битмапку размером не больше чем 8192х5120,  в противном случае получаю ошибки "Cannot create texture for 'TCanvasD2D'" или "Stack overflow"

 

Позже попробую поэкспериментировать на десктопе, растянутом на 2 монитора с портретной и ландшафтной ориентацией.

Изменено пользователем dnekrasov
Ссылка на комментарий
  • 0
В 19.05.2016 в 22:57, Евгений Корепов сказал:

Я смирился. У меня в приложении с ~20000 пользователями ошибка вылезает у 290 :-( Т.е. это примерно 1.5%. 

попробуй переставь

ABitmapSurfaceResize:=TBitmapSurface.Create;

после 

ABitmapSurface:=TBitmapSurface.Create;

и

после определения максимальных длины и ширины  - сделай ABitmapSurfaceResize.SetSize

у меня по крайней мере заработало.

 

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

в общем итоговая процедура получилась у меня вот так, при этом заработало там где до этого не работало

procedure CheckAndLoadFromStream(FileName:string; const ABitmap : TBitmap);
var
  MaxImageSize : Integer;
  ABitmapSurface,ABitmapSurfaceResize : TBitmapSurface;
  mxH,mxW:integer;
begin
  ABitmapSurface:=TBitmapSurface.Create;
  ABitmapSurfaceResize:=TBitmapSurface.Create;
  TBitmapCodecManager.LoadFromFile(FileName,ABitmapSurface);
  MaxImageSize:=TCanvasManager.DefaultCanvas.GetAttribute(TCanvasAttribute.MaxBitmapSize);
  if (ABitmapSurface.Height>MaxImageSize) or (ABitmapSurface.Width>MaxImageSize) then
  begin
    if ABitmapSurface.Height>ABitmapSurface.Width then
    begin
      mxH:=MaxImageSize;
      mxW:=Round(mxH/ABitmapSurface.Height*ABitmapSurface.Height);
    end else
      begin
        mxW:=MaxImageSize;
        mxH:=Round(mxW/ABitmapSurface.Width*ABitmapSurface.Height);
      end;
  end else
    begin
      mxW:=ABitmapSurface.Width;
      mxH:=ABitmapSurface.Height;
    end;
  ABitmapSurfaceResize.SetSize(mxW,mxH);
  ABitmapSurfaceResize.StretchFrom(ABitmapSurface,mxW,mxH);
  FreeAndNil(ABitmapSurface);
  ABitmap.SetSize(mxW,mxH);
  ABitmap.Assign(ABitmapSurfaceResize);
  FreeAndNil(ABitmapSurfaceResize);
end;
Изменено пользователем tromani
Ссылка на комментарий
  • 0

К сожалению этот код выполняется неимоверно долго. На моём HTC One, загрузка с локального хранилища 30 картинок 250х250 производится за 15-20 секунд. Прямая операция Assigned(ABitmap) происходит фактически мгновенно. Так что ради полутора процентов пользователей, замедлять работу приложения на два порядка я не готов.

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

Посмотрел исходники, оказывается там все это уже есть:

unit FMX.Graphics;

procedure TBitmap.AssignFromSurface(const Source: TBitmapSurface);
var
  BitmapData: TBitmapData;
  MaxSize: Integer;
  ResampledSurface: TBitmapSurface;
  I: Integer;
  SourceRect: TRectF;
begin
  MaxSize := CanvasClass.GetAttribute(TCanvasAttribute.MaxBitmapSize);
  if (Source.Width > MaxSize) or (Source.Height > MaxSize) then
  begin
    SourceRect := TRectF.Create(0, 0, Source.Width, Source.Height);
    SourceRect.Fit(TRectF.Create(0, 0, MaxSize, MaxSize));
    ResampledSurface := TBitmapSurface.Create;
    try
      ResampledSurface.StretchFrom(Source, Trunc(SourceRect.Width), Trunc(SourceRect.Height), PixelFormat);
      AssignFromSurface(ResampledSurface);
    finally
      ResampledSurface.Free;
    end;
  end
  else
  begin
    SetSize(Source.Width, Source.Height);
    if Map(TMapAccess.Write, BitmapData) then
    try
      for I := 0 to TBitmapSurface(Source).Height - 1 do
        Move(TBitmapSurface(Source).Scanline[I]^, BitmapData.GetScanline(I)^, BitmapData.BytesPerLine);
    finally
      Unmap(BitmapData);
    end;
  end;
end;

Так что весь мой код не имеет смысла. В процедуре можно ограничится следующим кодом:

procedure TFormMain.CheckAndLoadFromStream(const AStream : TStream; const ABitmap : TBitmap);
Var ABitmapSurface : TBitmapSurface;
begin
  ABitmapSurface:=TBitmapSurface.Create;
  AStream.Position:=0;
  TBitmapCodecManager.LoadFromStream(AStream,ABitmapSurface);
  if Assigned(ABitmap) then
    ABitmap.Assign(ABitmapSurface);
  FreeAndNil(ABitmapSurface);
end;

Ну и изредка глючить он будет по прежнему...

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

хм... ну очевидно что медленность работы вызвана 

ABitmapSurfaceResize.StretchFrom(ABitmapSurface,mxW,mxH);

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

 

Я конечно не знаю как у вас происходит но меня очень напрягает когда 2-3% скачавших приложение ставят оценку 1 потому что что-то не заработало или они не разобрались как работает

 

 

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

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

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

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

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

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

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

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

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

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