Модераторы: Snowy, Alexeis, MetalFan
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> GDI+ отредактировать многостраничный TIFF, GDI+ как с нею работать 
:(
    Опции темы
AnTeml
Дата 1.8.2013, 16:18 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 29
Регистрация: 19.3.2009

Репутация: нет
Всего: нет



Здравствуйте!
Возникла задача, имеется многостраничный TIFF-файл, нужно на каждой страничке подрисовать "печать" - небольшое изображение.

 TIFF файлы - отсканированные чертежи и текстовые документы в ч/б, многие многостраничные. Средний размер страницы в пикселах 4000*2516, dpi колеблется от 300 до 600 (причём в одном файле могут находиться страницы, отсканированные в разное время, с разным dpi), файлов очень много, задача запускается периодически оператором

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

После недолгих мытарств, была выбрана библиотека GDI+ : как никак microsoft, да и отобразить странички сходу получилось довольно просто.

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

Первая загадка встретилась, как ни странно, на винграде: 
статья "Использование декодеров и енкодеров GDI+ для загрузки и сохранения изображений",  http://forum.vingrad.ru/faq/topic-157721.html

В ней описывается достаточно громоздкий способ загрузки в TBitmap

uses GDIPAPI, GDIPOBJ, GDIPUTIL;

var
 Bitmap : TBitmap;
 
Код

procedure LoadFromFile(aFile : AnsiString);
var
  Filejpg      : TGPImage;//Класс GDI+ обеспечивающий загрузку и сохранение изображений
  mem          : TMemoryStream;//Поток в памяти, который будет получать Битмап
  aptr         : IStream;//Интерфейс который будет реализован при помощи TStreamAdapter
  encoderClsid : TGUID;//GUID - класса изображений
  n            : Int64;
begin
  aptr := nil;
  if FileExists(aFile)
  then
    begin
      Filejpg       := TGPImage.Create(aFile);//создаем класс используя имя файла
      Bitmap        := TBitmap.Create;//Создаем битмап
      mem           := TMemoryStream.Create;//Создаем класс потока
      aptr          := TStreamAdapter.Create(mem, soReference) as IStream;
   //Создаем класс адаптер, который все фунции интерфейса реализует при помощи нашего 
   //потока в памяти mem.
      GetEncoderClsid('image/bmp', encoderClsid);//Получаем GUID битмапа
      Filejpg.Save(aptr, encoderClsid);//Сохраняем изображение в поток
      aptr.Seek(0, 0, n);//Сдвигаем указатель потока в начало.
      Bitmap.LoadFromStream(mem);//Загружаем данные из потока в Битмап
      Filejpg.Free;//уничтожаем класс изображения
      aptr := nil;//Освобождаем интерфейс
      mem.Free;//уничтожаем поток в памяти.
    end;
end;


Однако, тот же самый результат, т.е. загрузить GpImage в Tbitmap, можно получить буквально парой строк:

Код

var
  Image: TGPImage;
  Graphics: TGPGraphics;
  tmp: TBitmap;
begin

  Image := TGPImage.Create(AFile);
  tmp := TBitmap.Create;
  tmp.Height := Image.GetHeight;
  tmp.Width := Image.GetWidth;

  Graphics := TGPGraphics.Create(tmp.Canvas.Handle);
  Graphics.DrawImage(Image, 0, 0, Image.GetWidth, Image.GetHeight);
end;

Две последних строчки примера "штатно" выгружают GPImage в TBitmap 
Для чего такие сложности с использованием дополнительных потоков и классов-адаптеров?
Быть может, я что-то недопонял? Или это просто альтернативный способ?

Сразу оговорюсь, что с графикой windows опыта работы практически нет. Возможно, большинство моих вопросов из-за этого :(

Дальше - больше. Теперь с сохранением в файл. Как я понял логику, 

объект GPImage нужно "листать", делая активной нужную страницу (Frame), и сохранять.
Если первая страница - вызывается метод Save, передавая через структуру параметров EncoderValueMultiFrame
Если последнующие - SaveAdd, с EncoderValueFrameDimensionPage, 
по окончании завершить, с EncoderValueFlush

В одной из лучшей информации, статьи на эту тему пересохраняют многостраничный PDF в многостраничны TIFF
http://www.gnostice.com/nl_article.asp?id=...mages_In_Delphi

Однако, оба примера, приведённые в статье, делают это мудрёным способом:
Они читают PDF по одной страничке, при этом каждую: 

1. сохраняют в объект TempTiff типа TGPimage, этот объект сохраняется в файл на диске (!) сам объект удаляется. 

2. Следующим шагом создаётся новый объект AdditionalFrameTiff, имеющий тип тоже GpImage, и он загружается из этого временного файла (!!) 
Этот объект добавляется как новая страница в MasterTIFF (тоже объект TGPImage).

зачем это это делается? Почему не использовать сразу первый объект TempTiff?

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

Во-первых, структура TEncoderParameters, которая передаётся при сохранении файла
в заголовочном файле она определена так:

Код

  EncoderParameters = packed record
    Count     : UINT;               // Number of parameters in this structure
    Parameter : array[0..0] of TEncoderParameter;  // Parameter values
  end;

Таким образом, более одного параметра передать невозможно. И плевать на значение свойства Count 
Мне нужно передать два параметра: помимо признака "мультистраничности" - и тип енкодера.
Гуглил несколько часов - ничего подходящего не нашёл. На сях видимо параметры определены по-другому, во встреченных примерах на делфи всюду передают только один параметр.

Забил "костыль", в заголовочном файле написал:   Parameter : array[0..1] of TEncoderParameter;

Всё заработало. Но это кривое решение не радует.

И остаётся главная проблема. Каким же образом мне "дорисовать" мою печать на конкретной страничке файла?

С большими надеждами загрузил TIFF файл не  в сам TGPImage, а в его наследника  - TGPBitmap
заявляются большие возможности, как доступа к пикселам, так и к отрисовке на нём
Но после того, как я сделал активной нужную страницу, и попытался что-то сделать с объектом, например, повернуть его - все остальные страницы, кроме текущей, из документа исчезли!

Код

    Image.SelectActiveFrame(FrameDimensionPage, 2);
    ShowMessage(inttostr(Image.GetFrameCount(FrameDimensionPage))); // выдаёт "4"
     (Image as TGPBitmap).RotateFlip(Rotate90FlipNone);
    ShowMessage(Image.GetFrameCount(FrameDimensionPage))); // выдаёт "1"


как же это всё барахло работает? Неужели действительно ничего не остаётся, как выгружать каждую страницу в TBitmap, редактировать, затем сохранять в промежуточный файл, потом загружать из файла и добавлять в новый объект? Может, не зря на gnotice.com извращаются именно так, сохраняя pdf в tiff?

нет ли хороших статей на тему работы с GDI+ ? 
Нельзя ли решить мою задачу, изучив какой-нибудь минимум информации, или без дебрей не обойтись?


Это сообщение отредактировал(а) AnTeml - 1.8.2013, 16:19
PM MAIL   Вверх
Alexeis
Дата 1.8.2013, 17:12 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 55
Всего: 459



Цитата(AnTeml @  1.8.2013,  17:18 Найти цитируемый пост)
Две последних строчки примера "штатно" выгружают GPImage в TBitmap 
Для чего такие сложности с использованием дополнительных потоков и классов-адаптеров?
Быть может, я что-то недопонял? Или это просто альтернативный способ?

   Загрузить файл в TBitmap и нарисовать изображение на нем это несколько разные вещи. Сохранение данных в поток осуществляется в том виде в котором настроен кодировщик. Рисование это уже операция преобразования в текущий формат графического контекста (по умолчанию TBitmap создается как DeviceDependent). Т.е. формат кодирования уже задает графический контекст. В общем случае это не так универсально по сравнению с кодировщиком. TStream же это универсальный VCL класс потока с которым умеют работать многие классы в VCL, например класс TBitmap . 
  Т.е. имеем схему 
|Файл| -> |GDI+ загрузчик| -> |GDI+ encoder| -> |Поток с универсальным результатом| -> |приемник результата (например TBitmap)|
  Частные решения всегда получаются проще, но подходят только в частных случаях. Например, заменили encoder на png и все, простое решение уже не работает, потому как фишка в том, что GDI+ имеет множество энкодеров позволяющих сохранять картинки в различных форматах с разными настройками, тогда как рисование загруженного изображения конвертирует лишь в Bitmap/DDB/принтер .

   
Цитата(AnTeml @  1.8.2013,  17:18 Найти цитируемый пост)
1. сохраняют в объект TempTiff типа TGPimage, этот объект сохраняется в файл на диске (!) сам объект удаляется. 

2. Следующим шагом создаётся новый объект AdditionalFrameTiff, имеющий тип тоже GpImage, и он загружается из этого временного файла (!!) 
Этот объект добавляется как новая страница в MasterTIFF (тоже объект TGPImage).

зачем это это делается? Почему не использовать сразу первый объект TempTiff?

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


Для чего они пересохраняют, трудно сказать, нужно будет почитать, но сходу напрашивается оптимизация как перегрузка не в файл, а в буфер в памяти, через тот же TMemoryStream. Скорее всего TGPImage "приобретает" свой формат при загрузке в него картинки, сохранение же в другом формате не меняет исходный тип. Но это не точно, нужно подробнее изучать.  

Цитата(AnTeml @  1.8.2013,  17:18 Найти цитируемый пост)
Таким образом, более одного параметра передать невозможно. И плевать на значение свойства Count 
Мне нужно передать два параметра: помимо признака "мультистраничности" - и тип енкодера.
Гуглил несколько часов - ничего подходящего не нашёл. На сях видимо параметры определены по-другому, во встреченных примерах на делфи всюду передают только один параметр.

Забил "костыль", в заголовочном файле написал:   Parameter : array[0..1] of TEncoderParameter;

Всё заработало. Но это кривое решение не радует.

Тут все просто. Ну нужно ничего менять в описании record. Когда мы видим array[0..0] это значит, что ожидается, что будут использовать указатель  ^TEncoderParameters. Т.е. выделят памяти столько сколько хватит на все параметры. Можно сделать проще, объявить массив типа TEncoderParameters на нужное число параметров и в наглую выходить за границу диапазона массива Parameter . Ведь элементы массива идут один за другим, а памяти в массиве типа TEncoderParameters будет предостаточно для размещения нужного числа TEncoderParameter .




--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
AnTeml
Дата 1.8.2013, 18:15 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 29
Регистрация: 19.3.2009

Репутация: нет
Всего: нет



Цитата(Alexeis @  1.8.2013,  17:12 Найти цитируемый пост)
  Загрузить файл в TBitmap и нарисовать изображение на нем это несколько разные вещи. Сохранение данных в поток осуществляется в том виде в котором настроен кодировщик. Рисование это уже операция преобразования в текущий формат графического контекста (по умолчанию TBitmap создается как DeviceDependent). Т.е. формат кодирования уже задает графический контекст. В общем случае это не так универсально по сравнению с кодировщиком. TStream же это универсальный VCL класс потока с которым умеют работать многие классы в VCL, например класс TBitmap . 
В общем случае понятно, насколько я понял, пример загрузки через потоки был дан ради примера работы. 

В данной ситуации? На входе обеих функций - GPImage, на выходе - только что созданный TBitmap, не связанный ни с каким устройством, в обоих случаях, т.е. функция должна отработать одинаково? Ведь в поток распаковывают тем же декодером, что и отрисовывают..?

Ну в принципе, это уже не особо важно. Я в обоих вариантах наткнулся на проблемы.
Вариант с промежуточными потоками удивил сразу. 
функцию оставил как в примере на винграде, даже комментарии, только переименовал её
Код

function CreateBitmapFromGpimage(Image: TGPImage): TBitmap;
var
  mem: TMemoryStream; //Поток в памяти, который будет получать Битмап
  aptr: IStream; //Интерфейс который будет реализован при помощи TStreamAdapter
  encoderClsid: TGUID; //GUID - класса изображений
  n: Int64;
begin
  aptr := nil;
  Result := TBitmap.Create;
  mem := TMemoryStream.Create; //Создаем класс потока
  aptr := TStreamAdapter.Create(mem, soReference) as IStream;
     //Создаем класс адаптер, который все фунции интерфейса реализует при помощи нашего
     //потока в памяти mem.
  GetEncoderClsid('image/bmp', encoderClsid); //Получаем GUID битмапа
  Image.Save(aptr, encoderClsid); //Сохраняем изображение в поток
  aptr.Seek(0, 0, n); //Сдвигаем указатель потока в начало.
  Result.LoadFromStream(mem); //Загружаем данные из потока в Битмап
  Image.Free; //уничтожаем класс изображения
  aptr := nil; //Освобождаем интерфейс
  mem.Free; //уничтожаем поток в памяти.
end;

procedure TfmMain.btn_CreBmpNewClick(Sender: TObject);
var
  Image: TGPImage;
  Graphics: TGPGraphics;
begin
  Image := TGPImage.Create(lblFileName.Caption);
  Image.SelectActiveFrame(FrameDimensionPage, sePageNum.Value);

  ShowMessage(IntToStr(Image.GetHeight)); // выдаёт правильную высоту, 3000

  bmp := CreateBitmapFromGpimage(Image);

  ShowMessage(IntToStr(Image.GetHeight)); // Access violation at address 00B401BD. Write of address 70B569C2
end;

Почему Image.GetHeight перестало работать? Ведь в функции получения битмапа создаётся класс потока, класс адаптера, сохраняется в битмап и потом все промежуточные объекты удаляются..

Ладно, пробую вариант с отрисовкой:

Код

var
  Image: TGPImage;
  Graphics: TGPGraphics;
begin
  Image := TGPImage.Create(lblFileName.Caption);
  Image.SelectActiveFrame(FrameDimensionPage, sePageNum.Value);

  bmp := TBitmap.Create;
  bmp.Height := Image.GetHeight;
  bmp.Width := Image.GetWidth;

  Graphics := TGPGraphics.Create(bmp.Canvas.Handle);
  Graphics.DrawImage(Image, 0, 0, Image.GetWidth, Image.GetHeight);

  FreeAndNil(Graphics);
  FreeAndNil(bmp);
  FreeAndNil(Image);
end;
Последние две строчки с освобождением объектов добавил, после того, как начались страшенные утечки памяти (файлы у меня с большим разрешением). Память начинает выжираться после выполнения Graphics.DrawImage(Image, 0, 0, Image.GetWidth, Image.GetHeight);

По всей видимости, эта функция, отрисовывая текущую страницу на Graphics, т.е. в TBitmap, добавляет в объект Image промежуточные распакованные данные, т.к. после удаления Graphics и bmp память не освобождается, и освобождается она только после удаления Image

Как принудительно "освободить" Image от этого мусора я не знаю. Похоже, придётся удалять объект Image. 

Похоже, раскрывается загадка с использованием промежуточного файла. Вы правы! по всей видимости, авторы статьи не нашли иного способа ни изменить тип текущего объекта, ни возможности "правильно" инициализировать объект Image с нужным им форматом, поэтому тупо выгрузили в файл и загрузили из файла объект-страницу заново. 
И так для каждой страницы...

Кто задаёт такую "моду" на такое программирование? Откуда этот геморрой, или мне, далёкому от технических подробностей работы операционки, это лишь кажется геморроем, а на самом деле такие выкрутасы в порядке вещей? smile


Цитата(Alexeis @  1.8.2013,  17:12 Найти цитируемый пост)
Тут все просто. Ну нужно ничего менять в описании record. Когда мы видим array[0..0] это значит, что ожидается, что будут использовать указатель  ^TEncoderParameters. Т.е. выделят памяти столько сколько хватит на все параметры. Можно сделать проще, объявить массив типа TEncoderParameters на нужное число параметров и в наглую выходить за границу диапазона массива Parameter . Ведь элементы массива идут один за другим, а памяти в массиве типа TEncoderParameters будет предостаточно для размещения нужного числа TEncoderParameter .
Я сразу попробовал записать в EncodeParameters.Parameters[1] нужные значения, но компилятор не дал: ведь максимальное значение "0". Честно говоря, я не совсем понял, что нужно делать, чтобы выйти за границу, быть может завести переменную и вычислять значение "1", чтобы оптимизатор её не выпилил, и таким образом занести за границу массива? Отключать проверки выхода за диапазон не хочется..

Правильно ли я понимаю, что необходимость в таких нечитабельных костылях возникает из за несоответствия типов данных С++ и Delphi ? В С++ будет прозрачнее, или ещё хуже?

Это сообщение отредактировал(а) AnTeml - 1.8.2013, 18:27
PM MAIL   Вверх
AnTeml
Дата 1.8.2013, 19:02 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 29
Регистрация: 19.3.2009

Репутация: нет
Всего: нет



Прошу прощения за эмоции, казалось бы простенькая задача но похоже за пару дней с GDI+ не разобраться, особенно без навыков программирования в windows

конкретизирую свой основной вопрос, больше к тем, кто с GDI+ имел дело (если есть такие)

каким образом можно загрузить страничку (пусть одну) TIFF из файла (размером ~10000*7000), нарисовать в ней пару линий (в идеале - получить доступ к битовой карте, т.е. прочитать значение пиксела, например, с координатами [10,10] и нарисовать (!) свой пиксел, например, с координатами [20,20] цветом 0), и сохранить её в такой же файл (ну или объект Image), закодировав в таком же формате с теми же параметрами, что и в исходном?



Без километрового кода тут не обойтись?
PM MAIL   Вверх
Alexeis
Дата 1.8.2013, 19:12 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 55
Всего: 459



Цитата(AnTeml @  1.8.2013,  19:15 Найти цитируемый пост)
В данной ситуации? На входе обеих функций - GPImage, на выходе - только что созданный TBitmap, не связанный ни с каким устройством, в обоих случаях, т.е. функция должна отработать одинаково? Ведь в поток распаковывают тем же декодером, что и отрисовывают..?

  Да, это частное решение работает аналогично, возможно даже быстрее.

Цитата(AnTeml @  1.8.2013,  19:15 Найти цитируемый пост)
Почему Image.GetHeight перестало работать? Ведь в функции получения битмапа создаётся класс потока, класс адаптера, сохраняется в битмап и потом все промежуточные объекты удаляются..

  Потому что внутри CreateBitmapFromGpimage делается ненужная операция удаления объекта Image: TGPImage который ему передали
Цитата

 Result.LoadFromStream(mem); //Загружаем данные из потока в Битмап
  Image.Free; //уничтожаем класс изображения
 

У меня объект рождался и уничтожался внутри функции. Если объект создается во вне, то он должен также уничтожаться во вне.

Цитата(AnTeml @  1.8.2013,  19:15 Найти цитируемый пост)
Кто задаёт такую "моду" на такое программирование? Откуда этот геморрой, или мне, далёкому от технических подробностей работы операционки, это лишь кажется геморроем, а на самом деле такие выкрутасы в порядке вещей? 

  Возможно не предполагался такой режим использования. Насколько я помню с библиотекой GDI+ для делфи поставляется вагон примеров. Возможно, авторы библиотеки предполагали иной способ решения задачи. К сожалению с многостраничными картинками я не работал. Не знаю замысла авторов системы. Но перекачка через промежуточные буфера в памяти путем помещения в поток сравнительно быстрая, поскольку она не задействует железо (жесткий диск). Единственные затраты на создание правильных заголовков.


Цитата(AnTeml @  1.8.2013,  19:15 Найти цитируемый пост)
Я сразу попробовал записать в EncodeParameters.Parameters[1] нужные значения, но компилятор не дал: ведь максимальное значение "0". Честно говоря, я не совсем понял, что нужно делать, чтобы выйти за границу, быть может завести переменную и вычислять значение "1", чтобы оптимизатор её не выпилил, и таким образом занести за границу массива? Отключать проверки выхода за диапазон не хочется..

Правильно ли я понимаю, что необходимость в таких нечитабельных костылях возникает из за несоответствия типов данных С++ и Delphi ? В С++ будет прозрачнее, или ещё хуже?

  Да, предполагается запись в цикле через переменную. В С++ будет почти также, за исключением того, что компилятор не будет проверять границы диапазонов. В С++ нет способа задать структуру с динамическим массивом так чтобы массив был продолжением структуры. Это даже скорее не С++, а наследие С . В языке С модно было запихивать массив в конец структуры, для того чтобы потом выделять под нее памяти столько сколько нужно всем элементам + полям. 


--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
AnTeml
Дата 2.8.2013, 15:29 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 29
Регистрация: 19.3.2009

Репутация: нет
Всего: нет



Цитата(Alexeis @  1.8.2013,  19:12 Найти цитируемый пост)
  Да, это частное решение работает аналогично, возможно даже быстрее.
Я нашёл огромные плюсы вашего "частного решения"! (Алексей, я только сейчас обратил внимание, что автор статьи на винграде - Вы) smile

Перед отрисовкой на битмапе, с использованием Graphics.DrawImage, мне необходимо этот битмап создать. И я его создаю, с настройками "с потолка":


Код
G  bmp := TBitmap.Create;
  bmp.Monochrome := True;
  bmp.Height := ImgTMP.GetHeight;
  bmp.Width := ImgTMP.GetWidth;
  Graphics := TGPGraphics.Create(bmp.Canvas.Handle);
  Graphics.DrawImage(ImgTMP, 0, 0, ImgTMP.GetWidth, ImgTMP.GetHeight);

У меня есть много страничек, с разрешением ~10.000*7.000, по умолчанию в памяти создаётся битмап огроменного размера, а то и программа вываливается с сообщением о нехватке памяти. 

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

прошу прощения за "корявость" использования терминов, как я уже писал, я в графике да и в winapi ни в зуб ногой :(



Цитата(Alexeis @  1.8.2013,  19:12 Найти цитируемый пост)
Потому что внутри CreateBitmapFromGpimage делается ненужная операция удаления объекта Image: TGPImage который ему передали
Спасибо!! Всё верно! Конечно, когда руки кривые - нечего на зеркало пенять, я сейчас про себя. Я "допилил" вашу функцию, забыв удалить из неё удаление Image. После устранения этой ошибки, она отлично заработала.


Цитата(Alexeis @  1.8.2013,  19:12 Найти цитируемый пост)
Возможно не предполагался такой режим использования. Насколько я помню с библиотекой GDI+ для делфи поставляется вагон примеров. Возможно, авторы библиотеки предполагали иной способ решения задачи. К сожалению с многостраничными картинками я не работал. Не знаю замысла авторов системы. Но перекачка через промежуточные буфера в памяти путем помещения в поток сравнительно быстрая, поскольку она не задействует железо (жесткий диск). Единственные затраты на создание правильных заголовков.
Уфф..... Убился сегодня разгадывать замыслы авторов системы. Возможно, стоило бросить эксперименты и начать искать тонны мануалов с вагонами примеров... 
Но похоже, частично победил и методом научного тыка.

Но естественно, возникло множество загадок. Прежде всего, очень загадочно работают многие функции в плане, непонятно когда, куда и почему утекает память. К примеру, после упомянутого 
Код

 bmp := TBitmap.Create;
  bmp.Monochrome := True;
  bmp.Height := ImgTMP.GetHeight;
  bmp.Width := ImgTMP.GetWidth;
  Graphics := TGPGraphics.Create(bmp.Canvas.Handle);
  Graphics.DrawImage(ImgTMP, 0, 0, ImgTMP.GetWidth, ImgTMP.GetHeight);
программа занимает кучу памяти. Удаление объектов bmp и Graphics практически ничего не даёт. И только удаление ImgTMP позволяет освободить память. Но по логике - я его просто отрисовал на битмапе! Битмап потом удалил! Почему сам объект, который рисовал, занимает в памяти кучу места? Быть может, в нём хранятся какие-нибудь промежуточные преобразования.....

Но самое главное - после этих операций ImgTMP перестаёт давать "переключать" свои страницы.....
А если я загружаю не в IMAGE, а в наследника - TGPBitmap, делаю текущей какую-то страницу, а затем провожу штатную операцию GPBitmap-а, например, поворачиваю её: (Image as TGPBitmap).RotateFlip(Rotate90FlipNone)
то вуаля! страничка повёрнута, но Image.GetFrameCount(FrameDimensionPage) возвращает уже число страниц: 1 (до запуска поворота возвращало 4, что правильно). В общем, в объекте остаётся одна-единственная страничка, над которой было преобразование.
Предположу, об этом тоже где-то в документации предупреждается.....

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

В общем, результат:
Процедура, пересохраняющая TIFF файл, добавляющая на каждую страницу нового по случайной линии:

Логика следующая: Каждая страница сохраняется в битмап (та самая модифицированная ваша процедура, теперь она называется CreateBitmapFromGpimage), на битмап выводится произвольная линия, из битмапа она сохраняется в файл (процедура по мотивам вашей второй, теперь она называется SaveBmpToTiffFile), из временного файла она загружается в новый ImgChangedFrame := TGPImage.Create(TempFileName), затем этот объект добавляется как новая страничка к сохраняемому:
Код

procedure ReplaceTiffFile_UseTempFile(SourceFileName, ResultFileName: string);
  function CreateBitmapFromGpimage(Image: TGPImage): TBitmap;
  var
    mem: TMemoryStream;
    aptr: IStream;
    encoderClsid: TGUID;
    n: Int64;
  begin
    aptr := nil;
    Result := TBitmap.Create;
    mem := TMemoryStream.Create;
    aptr := TStreamAdapter.Create(mem, soReference) as IStream;
    GetEncoderClsid('image/bmp', encoderClsid);
    Image.Save(aptr, encoderClsid);
    aptr.Seek(0, 0, n);
    Result.LoadFromStream(mem);
    aptr := nil;
    mem.Free;
  end;

  function SaveBmpToTiffFile(bmp: TBitmap; FileName: string): TStatus;
  var
    mem: TMemoryStream;
    aptr: IStream;
    encoderClsid: TGUID;
    ImgTMP: TGPImage;

    eParameters: TEncoderParameters;
    Param: EncoderValue;

  begin
    mem := TMemoryStream.Create;
    bmp.SaveToStream(mem);
    mem.Seek(0, soFromBeginning);
    aptr := TStreamAdapter.Create(mem, soReference) as IStream;
    ImgTMP := TGPImage.Create(aptr);

    GetEncoderClsid('image/tiff', encoderClsid);
    eParameters.Count := 1;
    eParameters.Parameter[0].NumberOfValues := 1;
    eParameters.Parameter[0].Guid := EncoderCompression;
    eParameters.Parameter[0].Type_ := EncoderParameterValueTypeLong;
    eParameters.Parameter[0].Value := @param;
    param := EncoderValueCompressionCCITT4;
    Result := ImgTMP.Save(FileName, encoderClsid, @eParameters);

    ImgTMP.Free;
    aptr := nil;
    mem.Free;
  end;

var
  ImgSource, ImgChangedFrame, ImgResult: TGPImage;
  BmpCurrentFrame: TBitmap;
  PagesCount: integer;
  PageNum: integer;

  encoderClsid: TGUID;
  eParameters: TEncoderParameters;
  eValue, eValueCompress: EncoderValue;
  TempFileName: string;
begin

  GetEncoderClsid('image/tiff', encoderClsid);

  eParameters.Count := 2;

  eParameters.Parameter[0].NumberOfValues := 1;
  eParameters.Parameter[0].Guid := EncoderSaveFlag;
  eParameters.Parameter[0].Type_ := EncoderParameterValueTypeLong;
  eParameters.Parameter[0].Value := @eValue;

  eParameters.Parameter[1].NumberOfValues := 1;
  eParameters.Parameter[1].Guid := EncoderCompression;
  eParameters.Parameter[1].Type_ := EncoderParameterValueTypeLong;
  eParameters.Parameter[1].Value := @eValueCompress;

  eValueCompress := EncoderValueCompressionCCITT4;


  PagesCount := -1;
  PageNum := 0;
  repeat
    ImgSource := TGPImage.Create(SourceFileName);

    if PagesCount < 0 then
      PagesCount := ImgSource.GetFrameCount(FrameDimensionPage);

    ImgSource.SelectActiveFrame(FrameDimensionPage, PageNum);


    {Create Bitmap}
    BmpCurrentFrame := CreateBitmapFromGpimage(ImgSource);

    {Change Bitmap}
    BmpCurrentFrame.Canvas.MoveTo(Random(BmpCurrentFrame.Width), Random(BmpCurrentFrame.Height));
    BmpCurrentFrame.Canvas.LineTo(Random(BmpCurrentFrame.Width), Random(BmpCurrentFrame.Height));

    {Create ImgFrame}
    TempFileName := AddSuffToFileName(ResultFileName, '_' + inttostr(PageNum));
    if SaveBmpToTiffFile(BmpCurrentFrame, TempFileName) <> Ok then
      ShowMessage(TempFileName + ' has not been saved');
    ImgChangedFrame := TGPImage.Create(TempFileName);

    if PageNum = 0 then
    begin
      ImgResult := ImgChangedFrame;
      eValue := EncoderValueMultiFrame;
      ImgResult.Save(ResultFileName, encoderClsid, @eParameters)
    end
    else
    begin
      eValue := EncoderValueFrameDimensionPage;
      ImgResult.SaveAdd(ImgChangedFrame, @eParameters);
      FreeAndNil(ImgChangedFrame);
    end;

    FreeAndNil(ImgSource);
    Inc(PageNum);
  until PageNum >= PagesCount;

  eValue := EncoderValueFlush;
  ImgResult.SaveAdd(@eParameters);
  FreeAndNil(ImgResult);
end;

Логика с использованием временных файлов взята у парней с сайта gnostice, из их статьи:  http://www.gnostice.com/nl_article.asp?id=...mages_In_Delphi

В процессе разработки стало ясно, почему они использую много временных файлов, по числу страниц в документе: с какого-то перепугу файл то ли блокируется, то ли что (я не проверял), и после создания первого Temp.Tif (объект ImgChangedFrame, который в него сохранял - удаляется!) - второй раз сохранить в этот файл не получается. Наверное он всё-таки заблокировался, я не стал искать причину, сделал по аналогии - N файлов, по числу страниц.

Только в этой процедуре не написано их удаление, потому что я таки модифицировал процедуру, используя потоки.
Для потоков, как оказалось, нет необходимости создавать целый "пул" временных потоков, достаточно одной переменной, итак, конечный код, привожу целиком:
Код

procedure ReplaceTiffFile_UseTempStream(SourceFileName, ResultFileName: string);
  function CreateBitmapFromGpimage(Image: TGPImage): TBitmap;
  var
    mem: TMemoryStream;
    aptr: IStream;
    encoderClsid: TGUID;
    n: Int64;
  begin
    aptr := nil;
    Result := TBitmap.Create;
    mem := TMemoryStream.Create;
    aptr := TStreamAdapter.Create(mem, soReference) as IStream;
    GetEncoderClsid('image/bmp', encoderClsid);
    Image.Save(aptr, encoderClsid);
    aptr.Seek(0, 0, n);
    Result.LoadFromStream(mem);
    aptr := nil;
    mem.Free;
  end;

  function SaveBmpToTiffStream(bmp: TBitmap): TStream;
  var
    mem: TMemoryStream;
    aptr, aptrOut: IStream;
    encoderClsid: TGUID;
    ImgTMP: TGPImage;

    eParameters: TEncoderParameters;
    Param: EncoderValue;
    n: Int64;
  begin
    mem := TMemoryStream.Create;
    bmp.SaveToStream(mem);
    mem.Seek(0, soFromBeginning);
    aptr := TStreamAdapter.Create(mem, soReference) as IStream;
    ImgTMP := TGPImage.Create(aptr);

    GetEncoderClsid('image/tiff', encoderClsid);
    eParameters.Count := 1;
    eParameters.Parameter[0].NumberOfValues := 1;
    eParameters.Parameter[0].Guid := EncoderCompression;
    eParameters.Parameter[0].Type_ := EncoderParameterValueTypeLong;
    eParameters.Parameter[0].Value := @param;
    param := EncoderValueCompressionCCITT4;

    Result := TMemoryStream.Create;
    aptr := TStreamAdapter.Create(Result, soReference) as IStream;
    ImgTMP.Save(aptr, encoderClsid, @eParameters);
    aptr.Seek(0, 0, n);
    ImgTMP.Free;
    aptr := nil;
    mem.Free;
  end;

var
  ImgSource, ImgChangedFrame, ImgResult: TGPImage;
  BmpCurrentFrame: TBitmap;
  PagesCount: integer;
  PageNum: integer;

  encoderClsid: TGUID;
  eParameters: TEncoderParameters;
  eValue, eValueCompress: EncoderValue;
  TempFileName: string;
  TempStream: TStream;
  TempAptr: IStream;
begin

  GetEncoderClsid('image/tiff', encoderClsid);

  eParameters.Count := 2;

  eParameters.Parameter[0].NumberOfValues := 1;
  eParameters.Parameter[0].Guid := EncoderSaveFlag;
  eParameters.Parameter[0].Type_ := EncoderParameterValueTypeLong;
  eParameters.Parameter[0].Value := @eValue;

  eParameters.Parameter[1].NumberOfValues := 1;
  eParameters.Parameter[1].Guid := EncoderCompression;
  eParameters.Parameter[1].Type_ := EncoderParameterValueTypeLong;
  eParameters.Parameter[1].Value := @eValueCompress;

  eValueCompress := EncoderValueCompressionCCITT4;


  PagesCount := -1;
  PageNum := 0;
  repeat
    ImgSource := TGPImage.Create(SourceFileName);

    if PagesCount < 0 then
      PagesCount := ImgSource.GetFrameCount(FrameDimensionPage);

    ImgSource.SelectActiveFrame(FrameDimensionPage, PageNum);


    {Create Bitmap}
    BmpCurrentFrame := CreateBitmapFromGpimage(ImgSource);

    {Change Bitmap}
    BmpCurrentFrame.Canvas.MoveTo(Random(BmpCurrentFrame.Width), Random(BmpCurrentFrame.Height));
    BmpCurrentFrame.Canvas.LineTo(Random(BmpCurrentFrame.Width), Random(BmpCurrentFrame.Height));

    {Create ImgFrame}
    TempStream := SaveBmpToTiffStream(BmpCurrentFrame);
    TempAptr := TStreamAdapter.Create(TempStream, soReference) as IStream;
    ImgChangedFrame := TGPImage.Create(TempAptr);

    if PageNum = 0 then
    begin
      ImgResult := ImgChangedFrame;
      eValue := EncoderValueMultiFrame;
      ImgResult.Save(ResultFileName, encoderClsid, @eParameters)
    end
    else
    begin
      eValue := EncoderValueFrameDimensionPage;
      ImgResult.SaveAdd(ImgChangedFrame, @eParameters);
      FreeAndNil(ImgChangedFrame);
    end;

    FreeAndNil(ImgSource);
    Inc(PageNum);
  until PageNum >= PagesCount;

  eValue := EncoderValueFlush;
  ImgResult.SaveAdd(@eParameters);
  FreeAndNil(ImgResult);
end;

В принципе, код рабочий. Но либо опять в нём присутствует какая-то глупая ошибка, либо это снова чудеса GDI+, но в нём имеется утечка памяти.
Файл создаётся, однако, несмотря на то, что вроде всё удалено, память не освобождается.

Причину пока не нашёл :(( если не успею найти, нужно будет прописать это как фичу программы в документации smile

Это сообщение отредактировал(а) AnTeml - 2.8.2013, 15:36
PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Delphi: Звук, графика и видео"
Girder
Snowy
Alexeis

Запрещено:

1. Публиковать ссылки на вскрытые компоненты

2. Обсуждать взлом компонентов и делится вскрытыми компонентами

  • Литературу по Дельфи обсуждаем здесь
  • Действия модераторов можно обсудить здесь
  • С просьбами о написании курсовой, реферата и т.п. обращаться сюда
  • Вопросы по реализации алгоритмов рассматриваются здесь
  • 90% ответов на свои вопросы можно найти в DRKB (Delphi Russian Knowledge Base) - крупнейшем в рунете сборнике материалов по Дельфи
  • По вопросам разработки игр стоит заглянуть сюда

FAQ раздела лежит здесь!


Если Вам помогли и атмосфера форума Вам понравилась, то заходите к нам чаще! С уважением, Girder, Snowy.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Delphi: Звук, графика и видео | Следующая тема »


 




[ Время генерации скрипта: 0.0561 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.