При пакетной обработке графических файлов — повороте, изменении разрешения, склейке или динамическом предпросмотре — разработчики нередко сталкиваются с ситуацией, когда процесс платформы быстро увеличивает объем занимаемой оперативной памяти. Достигнув отметки около 4 ГБ, приложение аварийно завершает работу с ошибкой нехватки памяти. В подобных случаях подозрение часто падает на внешние компоненты (например, GFLAx или сторонние COM-библиотеки). Однако детальный анализ показывает, что корень проблемы чаще всего кроется в особенностях работы графической подсистемы самой платформы 1С.
Разберем природу этой проблемы, проанализируем типичный сценарий возникновения утечки и рассмотрим надежные способы её устранения.
Рассмотрим стандартный сценарий: в цикле выполняется поворот файлов через COM-объект с последующим освобождением переменной:
КартинкаGFLAx = Новый COMОбъект("GFLAx.GFLAx");
КартинкаGFLAx.LoadBitmap(ПутьККартинке);
КартинкаGFLAx.Rotate(90);
КартинкаGFLAx.SaveBitmap(ПутьККартинке);
КартинкаGFLAx = Неопределено;
Если выполнять исключительно эту операцию, COM-объект корректно очищает дескрипторы памяти в операционной системе, и потребление ОЗУ процессом 1С остается стабильным. Однако на практике после модификации файла разработчик часто выводит результат пользователю на форму:
ВыбранноеФото = Новый Картинка(ПутьКФайлуКартинки, Ложь);
ЭлементыФормы.Предпросмотр.Картинка = ВыбранноеФото;
ВыбранноеФото = Неопределено;
Именно эта операция становится причиной утечки. Выясним причину такого поведения платформы:
Картинка в элемент формы графическая подсистема Windows выделяет системные дескрипторы (GDI-объекты HBITMAP). Внутри одного сеанса работы формы платформа 1С не всегда принудительно освобождает предыдущие растровые буферы, удерживая их до момента полного закрытия окна.Если для бизнес-логики требуется обязательное визуальное отображение результата пользователю на форме, категорически не рекомендуется передавать в элемент управления исходные тяжелые файлы с разрешением в несколько тысяч пикселей. Подготовим миниатюру (Thumbnail) специально для предпросмотра:
ВремПутьККартинке = ПолучитьИмяВременногоФайла("jpg");
КартинкаGFLAx = Новый COMОбъект("GFLAx.GFLAx");
КартинкаGFLAx.LoadBitmap(ПутьКФайлуКартинки);
ШиринаКартинки = КартинкаGFLAx.Width;
ВысотаКартинки = КартинкаGFLAx.Height;
// Рассчитываем пропорциональное уменьшение до размеров области предпросмотра
Если ШиринаКартинки > ВысотаКартинки Тогда
НужнаяШирина = 300;
НужнаяВысота = Окр(ВысотаКартинки * (300 / ШиринаКартинки));
Иначе
НужнаяВысота = 300;
НужнаяШирина = Окр(ШиринаКартинки * (300 / ВысотаКартинки));
КонецЕсли;
КартинкаGFLAx.Resize(НужнаяШирина, НужнаяВысота);
КартинкаGFLAx.SaveBitmap(ВремПутьККартинке);
КартинкаGFLAx = Неопределено;
// Загружаем в форму облегченную копию
ВыбранноеФото = Новый Картинка(ВремПутьККартинке, Ложь);
ЭлементыФормы.Предпросмотр.Картинка = ВыбранноеФото;
ВыбранноеФото = Неопределено;
УдалитьФайлы(ВремПутьККартинке);
Уменьшение разрешения кадра в десятки раз пропорционально снижает объем памяти, выделяемой под растровый буфер интерфейсного элемента, что позволяет избежать резких скачков потребления ресурсов.
Чтобы заставить контроллер формы принудительно освободить дескриптор предыдущего растра в памяти, перед установкой нового значения очистим поле пустым конструктором:
// Принудительно сбрасываем старый дескриптор в элементе управления
ЭлементыФормы.Предпросмотр.Картинка = Новый Картинка();
// Присваиваем новое изображение
ЭлементыФормы.Предпросмотр.Картинка = Новый Картинка(ПутьКФайлуКартинки, Ложь);
В сочетании с вызовом ОбработкаПрерыванияПользователя() в цикле это дает платформе возможность обработать очередь системных оконных сообщений и своевременно очистить освободившиеся дескрипторы.
Наиболее надежный интерфейсный способ избежать утечек при непрерывном показе графики — замена стандартного элемента формы на ПолеHTMLДокумента. Браузерный движок внутри элемента управления самостоятельно управляет жизненным циклом растровых объектов и корректно высвобождает память при смене источника:
ПутьДляHTML = СтрЗаменить(ПутьКФайлуКартинки, "\", "/");
ТекстHTML = "<html><body style='margin:0; padding:0; overflow:hidden;'>"
+ "<img src='file:///" + ПутьДляHTML + "' style='max-width:100%; max-height:100%; object-fit:contain;' />"
+ "</body></html>";
ЭлементыФормы.ПолеHTML.УстановитьТекст(ТекстHTML);
В этом случае платформа 1С не взаимодействует с графическими дескрипторами GDI напрямую, и память процесса не фрагментируется.
При обработке массива файлов (сотен или тысяч штук) визуальное обновление картинки на каждой итерации цикла существенно замедляет общую скорость работы и провоцирует утечки памяти. Рекомендуется разделить логику:
Если конфигурация работает на платформе версии 8.3.10 и выше, от использования внешней библиотеки GFLAx можно полностью отказаться в пользу встроенного объекта ОбрабатываемаяКартинка. Он доступен на стороне сервера, в толстом клиенте и не требует установки дополнительного стороннего ПО в операционной системе:
КартинкаИсточник = Новый Картинка(ПутьККартинке);
ОбработкаГрафики = Новый ОбрабатываемаяКартинка(КартинкаИсточник);
// Поворачиваем изображение на 90 градусов
ОбработкаГрафики.Повернуть(90);
// Получаем результирующий объект и сохраняем его на диск
ИтоговаяКартинка = ОбработкаГрафики.ПолучитьКартинку();
ИтоговаяКартинка.Записать(ПутьККартинке);
Объект ОбрабатываемаяКартинка также поддерживает штатное масштабирование через методы Масштабировать() и УстановитьРазмер(), кадрирование методом ВыделитьОбласть() и смену форматов через УстановитьФормат(), оптимизируя потребление ресурсов силами самой платформы.
Для систем, где применение штатного объекта невозможно, альтернативным решением является запуск консольных утилит (например, jpegtran) через метод ЗапуститьПриложение() с параметром ожидания завершения. При таком подходе трансформация файла выполняется в изолированном системном процессе, и вся выделенная память гарантированно возвращается операционной системе сразу после его закрытия.