![]() |
|
Модераторы: Snowy, Alexeis, MetalFan |
![]()
|
|
| AnTeml |
|
||||||||
|
Новичок Профиль Группа: Участник Сообщений: 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;
Однако, тот же самый результат, т.е. загрузить GpImage в Tbitmap, можно получить буквально парой строк:
Две последних строчки примера "штатно" выгружают 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, которая передаётся при сохранении файла в заголовочном файле она определена так:
Таким образом, более одного параметра передать невозможно. И плевать на значение свойства Count Мне нужно передать два параметра: помимо признака "мультистраничности" - и тип енкодера. Гуглил несколько часов - ничего подходящего не нашёл. На сях видимо параметры определены по-другому, во встреченных примерах на делфи всюду передают только один параметр. Забил "костыль", в заголовочном файле написал: Parameter : array[0..1] of TEncoderParameter; Всё заработало. Но это кривое решение не радует. И остаётся главная проблема. Каким же образом мне "дорисовать" мою печать на конкретной страничке файла? С большими надеждами загрузил TIFF файл не в сам TGPImage, а в его наследника - TGPBitmap заявляются большие возможности, как доступа к пикселам, так и к отрисовке на нём Но после того, как я сделал активной нужную страницу, и попытался что-то сделать с объектом, например, повернуть его - все остальные страницы, кроме текущей, из документа исчезли!
как же это всё барахло работает? Неужели действительно ничего не остаётся, как выгружать каждую страницу в TBitmap, редактировать, затем сохранять в промежуточный файл, потом загружать из файла и добавлять в новый объект? Может, не зря на gnotice.com извращаются именно так, сохраняя pdf в tiff? нет ли хороших статей на тему работы с GDI+ ? Нельзя ли решить мою задачу, изучив какой-нибудь минимум информации, или без дебрей не обойтись? Это сообщение отредактировал(а) AnTeml - 1.8.2013, 16:19 |
||||||||
|
|||||||||
| Alexeis |
|
|||
![]() Амеба Профиль Группа: Админ Сообщений: 11743 Регистрация: 12.10.2005 Где: Зеленоград Репутация: 55 Всего: 459 |
Загрузить файл в TBitmap и нарисовать изображение на нем это несколько разные вещи. Сохранение данных в поток осуществляется в том виде в котором настроен кодировщик. Рисование это уже операция преобразования в текущий формат графического контекста (по умолчанию TBitmap создается как DeviceDependent). Т.е. формат кодирования уже задает графический контекст. В общем случае это не так универсально по сравнению с кодировщиком. TStream же это универсальный VCL класс потока с которым умеют работать многие классы в VCL, например класс TBitmap . Т.е. имеем схему |Файл| -> |GDI+ загрузчик| -> |GDI+ encoder| -> |Поток с универсальным результатом| -> |приемник результата (например TBitmap)| Частные решения всегда получаются проще, но подходят только в частных случаях. Например, заменили encoder на png и все, простое решение уже не работает, потому как фишка в том, что GDI+ имеет множество энкодеров позволяющих сохранять картинки в различных форматах с разными настройками, тогда как рисование загруженного изображения конвертирует лишь в Bitmap/DDB/принтер . Для чего они пересохраняют, трудно сказать, нужно будет почитать, но сходу напрашивается оптимизация как перегрузка не в файл, а в буфер в памяти, через тот же TMemoryStream. Скорее всего TGPImage "приобретает" свой формат при загрузке в него картинки, сохранение же в другом формате не меняет исходный тип. Но это не точно, нужно подробнее изучать. Тут все просто. Ну нужно ничего менять в описании record. Когда мы видим array[0..0] это значит, что ожидается, что будут использовать указатель ^TEncoderParameters. Т.е. выделят памяти столько сколько хватит на все параметры. Можно сделать проще, объявить массив типа TEncoderParameters на нужное число параметров и в наглую выходить за границу диапазона массива Parameter . Ведь элементы массива идут один за другим, а памяти в массиве типа TEncoderParameters будет предостаточно для размещения нужного числа TEncoderParameter . -------------------- Vit вечная память. Обсуждение действий администрации форума производятся только в этом форуме гениальность идеи состоит в том, что ее невозможно придумать |
|||
|
||||
| AnTeml |
|
||||
|
Новичок Профиль Группа: Участник Сообщений: 29 Регистрация: 19.3.2009 Репутация: нет Всего: нет |
В общем случае понятно, насколько я понял, пример загрузки через потоки был дан ради примера работы. В данной ситуации? На входе обеих функций - GPImage, на выходе - только что созданный TBitmap, не связанный ни с каким устройством, в обоих случаях, т.е. функция должна отработать одинаково? Ведь в поток распаковывают тем же декодером, что и отрисовывают..? Ну в принципе, это уже не особо важно. Я в обоих вариантах наткнулся на проблемы. Вариант с промежуточными потоками удивил сразу. функцию оставил как в примере на винграде, даже комментарии, только переименовал её
Почему Image.GetHeight перестало работать? Ведь в функции получения битмапа создаётся класс потока, класс адаптера, сохраняется в битмап и потом все промежуточные объекты удаляются.. Ладно, пробую вариант с отрисовкой:
По всей видимости, эта функция, отрисовывая текущую страницу на Graphics, т.е. в TBitmap, добавляет в объект Image промежуточные распакованные данные, т.к. после удаления Graphics и bmp память не освобождается, и освобождается она только после удаления Image Как принудительно "освободить" Image от этого мусора я не знаю. Похоже, придётся удалять объект Image. Похоже, раскрывается загадка с использованием промежуточного файла. Вы правы! по всей видимости, авторы статьи не нашли иного способа ни изменить тип текущего объекта, ни возможности "правильно" инициализировать объект Image с нужным им форматом, поэтому тупо выгрузили в файл и загрузили из файла объект-страницу заново. И так для каждой страницы... Кто задаёт такую "моду" на такое программирование? Откуда этот геморрой, или мне, далёкому от технических подробностей работы операционки, это лишь кажется геморроем, а на самом деле такие выкрутасы в порядке вещей? Я сразу попробовал записать в EncodeParameters.Parameters[1] нужные значения, но компилятор не дал: ведь максимальное значение "0". Честно говоря, я не совсем понял, что нужно делать, чтобы выйти за границу, быть может завести переменную и вычислять значение "1", чтобы оптимизатор её не выпилил, и таким образом занести за границу массива? Отключать проверки выхода за диапазон не хочется.. Правильно ли я понимаю, что необходимость в таких нечитабельных костылях возникает из за несоответствия типов данных С++ и Delphi ? В С++ будет прозрачнее, или ещё хуже? Это сообщение отредактировал(а) AnTeml - 1.8.2013, 18:27 |
||||
|
|||||
| AnTeml |
|
|||
|
Новичок Профиль Группа: Участник Сообщений: 29 Регистрация: 19.3.2009 Репутация: нет Всего: нет |
Прошу прощения за эмоции, казалось бы простенькая задача но похоже за пару дней с GDI+ не разобраться, особенно без навыков программирования в windows
конкретизирую свой основной вопрос, больше к тем, кто с GDI+ имел дело (если есть такие) каким образом можно загрузить страничку (пусть одну) TIFF из файла (размером ~10000*7000), нарисовать в ней пару линий (в идеале - получить доступ к битовой карте, т.е. прочитать значение пиксела, например, с координатами [10,10] и нарисовать (!) свой пиксел, например, с координатами [20,20] цветом 0), и сохранить её в такой же файл (ну или объект Image), закодировав в таком же формате с теми же параметрами, что и в исходном? Без километрового кода тут не обойтись? |
|||
|
||||
| Alexeis |
|
|||
![]() Амеба Профиль Группа: Админ Сообщений: 11743 Регистрация: 12.10.2005 Где: Зеленоград Репутация: 55 Всего: 459 |
Да, это частное решение работает аналогично, возможно даже быстрее. Потому что внутри CreateBitmapFromGpimage делается ненужная операция удаления объекта Image: TGPImage который ему передали
У меня объект рождался и уничтожался внутри функции. Если объект создается во вне, то он должен также уничтожаться во вне. Возможно не предполагался такой режим использования. Насколько я помню с библиотекой GDI+ для делфи поставляется вагон примеров. Возможно, авторы библиотеки предполагали иной способ решения задачи. К сожалению с многостраничными картинками я не работал. Не знаю замысла авторов системы. Но перекачка через промежуточные буфера в памяти путем помещения в поток сравнительно быстрая, поскольку она не задействует железо (жесткий диск). Единственные затраты на создание правильных заголовков. Да, предполагается запись в цикле через переменную. В С++ будет почти также, за исключением того, что компилятор не будет проверять границы диапазонов. В С++ нет способа задать структуру с динамическим массивом так чтобы массив был продолжением структуры. Это даже скорее не С++, а наследие С . В языке С модно было запихивать массив в конец структуры, для того чтобы потом выделять под нее памяти столько сколько нужно всем элементам + полям. -------------------- Vit вечная память. Обсуждение действий администрации форума производятся только в этом форуме гениальность идеи состоит в том, что ее невозможно придумать |
|||
|
||||
| AnTeml |
|
||||||||||||
|
Новичок Профиль Группа: Участник Сообщений: 29 Регистрация: 19.3.2009 Репутация: нет Всего: нет |
Перед отрисовкой на битмапе, с использованием Graphics.DrawImage, мне необходимо этот битмап создать. И я его создаю, с настройками "с потолка":
У меня есть много страничек, с разрешением ~10.000*7.000, по умолчанию в памяти создаётся битмап огроменного размера, а то и программа вываливается с сообщением о нехватке памяти. однако в файлах битмапы как минимум монохромны, и что-то там ещё есть, т.о. при запуске вашего решения автоматически памяти под битмап минимум, файл в диспетчере задач занимает десятки мегабайт. Никаких проблем, и думать, какой битмап создавать - не нужно. прошу прощения за "корявость" использования терминов, как я уже писал, я в графике да и в winapi ни в зуб ногой :(
Уфф..... Убился сегодня разгадывать замыслы авторов системы. Возможно, стоило бросить эксперименты и начать искать тонны мануалов с вагонами примеров... Но похоже, частично победил и методом научного тыка. Но естественно, возникло множество загадок. Прежде всего, очень загадочно работают многие функции в плане, непонятно когда, куда и почему утекает память. К примеру, после упомянутого
Но самое главное - после этих операций ImgTMP перестаёт давать "переключать" свои страницы..... А если я загружаю не в IMAGE, а в наследника - TGPBitmap, делаю текущей какую-то страницу, а затем провожу штатную операцию GPBitmap-а, например, поворачиваю её: (Image as TGPBitmap).RotateFlip(Rotate90FlipNone) то вуаля! страничка повёрнута, но Image.GetFrameCount(FrameDimensionPage) возвращает уже число страниц: 1 (до запуска поворота возвращало 4, что правильно). В общем, в объекте остаётся одна-единственная страничка, над которой было преобразование. Предположу, об этом тоже где-то в документации предупреждается..... В общем, пришлось плюнуть на всё это и использовать классический TBitmap, вкупе с вашими преобразованиями его, посредством потоков. В общем, результат: Процедура, пересохраняющая TIFF файл, добавляющая на каждую страницу нового по случайной линии: Логика следующая: Каждая страница сохраняется в битмап (та самая модифицированная ваша процедура, теперь она называется CreateBitmapFromGpimage), на битмап выводится произвольная линия, из битмапа она сохраняется в файл (процедура по мотивам вашей второй, теперь она называется SaveBmpToTiffFile), из временного файла она загружается в новый ImgChangedFrame := TGPImage.Create(TempFileName), затем этот объект добавляется как новая страничка к сохраняемому:
Логика с использованием временных файлов взята у парней с сайта gnostice, из их статьи: http://www.gnostice.com/nl_article.asp?id=...mages_In_Delphi В процессе разработки стало ясно, почему они использую много временных файлов, по числу страниц в документе: с какого-то перепугу файл то ли блокируется, то ли что (я не проверял), и после создания первого Temp.Tif (объект ImgChangedFrame, который в него сохранял - удаляется!) - второй раз сохранить в этот файл не получается. Наверное он всё-таки заблокировался, я не стал искать причину, сделал по аналогии - N файлов, по числу страниц. Только в этой процедуре не написано их удаление, потому что я таки модифицировал процедуру, используя потоки. Для потоков, как оказалось, нет необходимости создавать целый "пул" временных потоков, достаточно одной переменной, итак, конечный код, привожу целиком:
В принципе, код рабочий. Но либо опять в нём присутствует какая-то глупая ошибка, либо это снова чудеса GDI+, но в нём имеется утечка памяти. Файл создаётся, однако, несмотря на то, что вроде всё удалено, память не освобождается. Причину пока не нашёл :(( если не успею найти, нужно будет прописать это как фичу программы в документации Это сообщение отредактировал(а) AnTeml - 2.8.2013, 15:36 |
||||||||||||
|
|||||||||||||
![]()
|
| Правила форума "Delphi: Звук, графика и видео" | |
|
|
Запрещено: 1. Публиковать ссылки на вскрытые компоненты 2. Обсуждать взлом компонентов и делится вскрытыми компонентами
FAQ раздела лежит здесь! Если Вам помогли и атмосфера форума Вам понравилась, то заходите к нам чаще! С уважением, Girder, Snowy. |
| 0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей) | |
| 0 Пользователей: | |
| « Предыдущая тема | Delphi: Звук, графика и видео | Следующая тема » |
|
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |