Как мне использовать большие растровые изображения в .NET?
-
05-09-2019 - |
Вопрос
Я пытаюсь написать легкое приложение для просмотра изображений.Однако в .NET существуют ограничения по системной памяти.
При попытке загрузить большие растровые изображения (9000 x 9000 пикселей или больше, 24-разрядный), я получаю System.Исключение OutOfMemoryException.Это на ПК с Windows 2000 и 2 ГБ оперативной памяти (из которых израсходовано 1,3 ГБ).Попытка загрузки файлов также занимает много времени.
Следующий код генерирует эту ошибку:
Image image = new Bitmap(filename);
using (Graphics gfx = this.CreateGraphics())
{
gfx.DrawImage(image, new Point(0, 0));
}
Как и этот код:
Stream stream = (Stream)File.OpenRead(filename);
Image image = Image.FromStream(stream, false, false);
using (Graphics gfx = this.CreateGraphics())
{
gfx.DrawImage(image, new Rectangle(0, 0, 100, 100), 4000, 4000, 100, 100, GraphicsUnit.Pixel);
}
Кроме того, достаточно сделать именно это:
Bitmap bitmap = new Bitmap(filename);
IntPtr handle = bitmap.GetHbitmap();
Последний код был предназначен для использования с GDI.Исследуя это, я обнаружил, что на самом деле это проблема с памятью, когда .NET пытается выделить в два раза больше, чем необходимо, в одном непрерывном блоке памяти.
http://bytes.com/groups/net-c/279493-drawing-large-bitmaps
Я знаю из других приложений (Internet Explorer, MS Paint и т.д.), что можно открывать большие изображения, и довольно быстро.Мой вопрос заключается в следующем, как мне использовать большие растровые изображения с .NET?
Есть ли какой-либо способ их потоковой передачи или загрузки без использования памяти?
Решение
Это вопрос, состоящий из двух частей.Первый вопрос заключается в том, как вы можете загружать большие изображения без нехватки памяти (1), второй - о повышении производительности загрузки (2).
(1) Выберите приложение, подобное Photoshop, где у вас есть возможность работать с огромными изображениями, занимающими гигабайты файловой системы.Сохранить все изображение в памяти и при этом иметь достаточно свободной памяти для выполнения операций (фильтры, обработка изображений и т.д. Или даже просто добавление слоев) было бы невозможно в большинстве систем (даже в системах 8gb x64).
Вот почему такие приложения, как это, используют концепцию файлов подкачки.Внутренне я предполагаю, что photoshop использует проприетарный формат файла, подходящий для дизайна их приложений и созданный для поддержки частичных загрузок из swap, что позволяет им загружать части файла в память для его обработки.
(2) Производительность может быть улучшена (довольно значительно) путем написания пользовательских загрузчиков для каждого формата файла.Для этого вам необходимо ознакомиться с заголовками файлов и структурой форматов файлов, с которыми вы хотите работать.Как только вы освоитесь с этим, это будет не так уж и сложно, но и не так тривиально, как выполнение вызова метода.
Например, вы могли бы поискать в Google FastBitmap, чтобы увидеть примеры того, как вы можете очень быстро загрузить файл bitmap (BMP), включая декодирование заголовка bitmap.Для этого был задействован PInvoke, и чтобы дать вам некоторое представление о том, с чем вы столкнулись, вам нужно будет определить структуры bitmap, такие как
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BITMAPFILEHEADER
{
public Int16 bfType;
public Int32 bfSize;
public Int16 bfReserved1;
public Int16 bfReserved2;
public Int32 bfOffBits;
}
[StructLayout(LayoutKind.Sequential)]
public struct BITMAPINFO
{
public BITMAPINFOHEADER bmiHeader;
public RGBQUAD bmiColors;
}
[StructLayout(LayoutKind.Sequential)]
public struct BITMAPINFOHEADER
{
public uint biSize;
public int biWidth;
public int biHeight;
public ushort biPlanes;
public ushort biBitCount;
public BitmapCompression biCompression;
public uint biSizeImage;
public int biXPelsPerMeter;
public int biYPelsPerMeter;
public uint biClrUsed;
public uint biClrImportant;
}
Возможно, поработайте с созданием DIB (http://www.herdsoft.com/ti/davincie/imex3j8i.htm) и странности, такие как данные, хранящиеся "вверх ногами" в растровом изображении, которые вам нужно учитывать, иначе вы увидите зеркальное отображение, когда откроете его :-)
Теперь это только для растровых изображений.Допустим, вы хотели сделать PNG, тогда вам нужно было бы сделать аналогичный материал, но расшифровать заголовок PNG, который в его простейшей форме не так сложен, но если вы хотите получить полную поддержку спецификации PNG, тогда вас ждет увлекательная поездка :-)
PNG отличается от растрового изображения, поскольку он использует формат на основе фрагментов, где у него есть "заголовки", которые вы можете использовать для поиска разных данных.Примером некоторых фрагментов, которые я использовал, играя с форматом, было
string[] chunks =
new string[] {"?PNG", "IHDR","PLTE","IDAT","IEND","tRNS",
"cHRM","gAMA","iCCP","sBIT","sRGB","tEXt","zTXt","iTXt",
"bKGD","hIST","pHYs","sPLT","tIME"};
Вам также нужно будет узнать о контрольных суммах Adler32 для файлов PNG.Таким образом, каждый формат файла, который вы хотели бы использовать, добавлял бы другой набор проблем.
Я действительно хотел бы привести более полные примеры исходного кода в своем ответе, но это сложная тема, и, честно говоря, я сам не реализовывал swap, поэтому не смог бы дать слишком много убедительных советов по этому поводу.
Короткий ответ заключается в том, что возможности обработки изображений в BCL не настолько высоки.Средний ответ состоял бы в том, чтобы попытаться выяснить, написал ли кто-нибудь библиотеку изображений, которая могла бы вам помочь, а длинный ответ заключался бы в том, чтобы засучить рукава и написать ядро вашего приложения самостоятельно.
Поскольку вы знаете меня в реальной жизни, вы знаете, где меня найти ;)
Другие советы
Для получения действительно исчерпывающего ответа я бы использовал Reflector, чтобы посмотреть исходный код Paint.NET (http://www.getpaint.net/);продвинутая программа для редактирования графики, написанная на C #.
(как указано в комментарии, Paint.NET раньше был открытым исходным кодом, но теперь является закрытым исходным кодом).
О чем:
Image image = new Bitmap(filename);
using (Graphics gfx = Graphics.FromImage(image))
{
// Stuff
}
просто быстрое исправление, у меня была та же проблема, я создал второй экземпляр bitmap и передал bitmap в конструктор.
Можете ли вы создать новое пустое растровое изображение тех же размеров и глубины цвета?Если это так, то вы, по крайней мере, знаете, что ваша среда может обрабатывать изображение.Тогда проблема заключается в подсистеме загрузки изображений, как, конечно, может быть и в указанной вами ссылке.
Я думаю, вы могли бы написать свои собственные загрузчики растровых изображений, но это большая работа для нетривиальных форматов, поэтому я бы не стал этого предлагать.
Возможно, существуют доступные библиотеки замены, которые решают эти проблемы с помощью стандартных загрузчиков?
То есть вы хотите сказать, что нехватка памяти вызвана не загрузкой растрового изображения, а рендерингом?
Если да, можете ли вы использовать Bitmap.LockBits, чтобы получить доступ к пикселям и самостоятельно создать базовую программу изменения размера изображения?
Одна вещь просто поразила меня.Вы рисуете все изображение, а не только видимую часть?Вы не должны рисовать большую часть изображения, чем вы показываете в своем приложении, используйте параметры x, y, width и heigth, чтобы ограничить область рисования.