Какое изображение в оттенках серого с наибольшей битовой глубиной я могу экспортировать из FreeImage?
Что касается контекста, я работаю над созданием топографической программы, которая требует относительно предельной детализации. Я не ожидаю, что файлы будут маленькими, и формально их не нужно просматривать на мониторе, просто они должны иметь очень высокое разрешение.
Я знаю, что большинство форматов изображений ограничены 8 битами на пиксель из-за стандартных ограничений для обоих мониторов (по разумной цене) и человеческого восприятия. Однако 2⁸ - это всего 256 возможных значений, что вызывает артефакты плато в восстановленном смещении. 2¹⁶ может быть достаточно близким при 65536 возможных значениях, которых я достиг.
Я использую FreeImage и DLang для создания данных, которые в настоящее время находятся на машине Linux Mint.
Однако, когда я перешел на 2³², мне казалось, что поддержка программного обеспечения улетучилась. Я попробовал TIFF в этой форме, и, похоже, ничто не могло его интерпретировать, либо показывало полностью (или в основном) прозрачное изображение (помня, что я не ожидал, что какой-либо монитор действительно поддерживает оттенки 2² канала), либо жалобы на то, что он невозможно декодировать данные RGB. Я полагаю, это потому, что предполагалось, что это изображение RGB или RGBA.
FreeImage достаточно хорошо документирован для большинства целей, но теперь мне интересно, какой одноканальный формат с максимальной точностью я могу экспортировать и как мне это сделать? Кто-нибудь может привести пример? Неужели я действительно ограничен 16-битным форматом изображения в любом типичном и непривычном формате изображений? Я знаю, что этого достаточно, скажем, для медицинской визуализации, но я уверен, что я не первый человек, который пытается прицелиться выше, и мы, научные деятели, можем быть довольно амбициозными в отношении нашего уровня точности...
Я допустил вопиющую ошибку в своем коде? Есть ли что-то еще, что я должен попробовать вместо этого для такой точности?
Вот мой код.
16-битный TIFF, который работал
void writeGrayscaleMonochromeBitmap(const double width, const double height) {
FIBITMAP *bitmap = FreeImage_AllocateT(FIT_UINT16, cast(int)width, cast(int)height);
for(int y = 0; y < height; y++) {
ubyte *scanline = FreeImage_GetScanLine(bitmap, y);
for(int x = 0; x < width; x++) {
ushort v = cast(ushort)((x * 0xFFFF)/width);
ubyte[2] bytes = nativeToLittleEndian(cast(ushort)(x/width * 0xFFFF));
scanline[x * ushort.sizeof + 0] = bytes[0];
scanline[x * ushort.sizeof + 1] = bytes[1];
}
}
FreeImage_Save(FIF_TIFF, bitmap, "test.tif", TIFF_DEFAULT);
FreeImage_Unload(bitmap);
}
32-битный TIFF, который действительно не работал
void writeGrayscaleMonochromeBitmap32(const double width, const double height) {
FIBITMAP *bitmap = FreeImage_AllocateT(FIT_UINT32, cast(int)width, cast(int)height);
writeln(width, ", ", height);
writeln("Width: ", FreeImage_GetWidth(bitmap));
for(int y = 0; y < height; y++) {
ubyte *scanline = FreeImage_GetScanLine(bitmap, y);
writeln(y, ": ", scanline);
for(int x = 0; x < width; x++) {
//writeln(x, " < ", width);
uint v = cast(uint)((x/width) * 0xFFFFFFFF);
writeln("V: ", v);
ubyte[4] bytes = nativeToLittleEndian(v);
scanline[x * uint.sizeof + 0] = bytes[0];
scanline[x * uint.sizeof + 1] = bytes[1];
scanline[x * uint.sizeof + 2] = bytes[2];
scanline[x * uint.sizeof + 3] = bytes[3];
}
}
FreeImage_Save(FIF_TIFF, bitmap, "test32.tif", TIFF_NONE);
FreeImage_Unload(bitmap);
}
Спасибо за любые указатели.
1 ответ
Для одного канала максимальное значение, доступное для FreeImage, составляет 32 бита, как FIT_UINT32. Однако формат файла должен быть способен на это, и на данный момент только TIFF, похоже, подходит для этой задачи (см. Стр. 104 Стэнфордской документации). Кроме того, большинство мониторов неспособны отображать более 8 битов на выборку, 12 в крайних случаях, поэтому очень сложно прочитать данные обратно и правильно их отобразить.
Модульный тест, включающий сравнение байтов перед маршалингом в растровое изображение и выборку из того же растрового изображения впоследствии, показывает, что данные на самом деле кодируются.
Чтобы напечатать данные в 16-битной шкале серого (в настоящее время поддерживается J2K, JP2, PGM, PGMRAW, PNG и TIF), вы должны сделать что-то вроде этого:
void toFreeImageUINT16PNG(string fileName, const double width, const double height, double[] data) {
FIBITMAP *bitmap = FreeImage_AllocateT(FIT_UINT16, cast(int)width, cast(int)height);
for(int y = 0; y < height; y++) {
ubyte *scanline = FreeImage_GetScanLine(bitmap, y);
for(int x = 0; x < width; x++) {
//This magic has to happen with the y-coordinate in order to keep FreeImage from following its default behavior, and generating
//the image upside down.
ushort v = cast(ushort)(data[cast(ulong)(((height - 1) - y) * width + x)] * 0xFFFF); //((x * 0xFFFF)/width);
ubyte[2] bytes = nativeToLittleEndian(v);
scanline[x * ushort.sizeof + 0] = bytes[0];
scanline[x * ushort.sizeof + 1] = bytes[1];
}
}
FreeImage_Save(FIF_PNG, bitmap, fileName.toStringz);
FreeImage_Unload(bitmap);
}
Конечно, вы захотите внести изменения в свой целевой тип файла. Чтобы экспортировать как 48-битный RGB16, сделайте это.
void toFreeImageColorPNG(string fileName, const double width, const double height, double[] data) {
FIBITMAP *bitmap = FreeImage_AllocateT(FIT_RGB16, cast(int)width, cast(int)height);
uint pitch = FreeImage_GetPitch(bitmap);
uint bpp = FreeImage_GetBPP(bitmap);
for(int y = 0; y < height; y++) {
ubyte *scanline = FreeImage_GetScanLine(bitmap, y);
for(int x = 0; x < width; x++) {
ulong offset = cast(ulong)((((height - 1) - y) * width + x) * 3);
ushort r = cast(ushort)(data[(offset + 0)] * 0xFFFF);
ushort g = cast(ushort)(data[(offset + 1)] * 0xFFFF);
ushort b = cast(ushort)(data[(offset + 2)] * 0xFFFF);
ubyte[6] bytes = nativeToLittleEndian(r) ~ nativeToLittleEndian(g) ~ nativeToLittleEndian(b);
scanline[(x * 3 * ushort.sizeof) + 0] = bytes[0];
scanline[(x * 3 * ushort.sizeof) + 1] = bytes[1];
scanline[(x * 3 * ushort.sizeof) + 2] = bytes[2];
scanline[(x * 3 * ushort.sizeof) + 3] = bytes[3];
scanline[(x * 3 * ushort.sizeof) + 4] = bytes[4];
scanline[(x * 3 * ushort.sizeof) + 5] = bytes[5];
}
}
FreeImage_Save(FIF_PNG, bitmap, fileName.toStringz);
FreeImage_Unload(bitmap);
}
Наконец, чтобы закодировать изображение в оттенках серого UINT32 (в настоящее время ограниченное только TIFF), вы должны сделать это.
void toFreeImageTIF32(string fileName, const double width, const double height, double[] data) {
FIBITMAP *bitmap = FreeImage_AllocateT(FIT_UINT32, cast(int)width, cast(int)height);
//DEBUG
int xtest = cast(int)(width/2);
int ytest = cast(int)(height/2);
uint comp1a = cast(uint)(data[cast(ulong)(((height - 1) - ytest) * width + xtest)] * 0xFFFFFFFF);
writeln("initial: ", nativeToLittleEndian(comp1a));
for(int y = 0; y < height; y++) {
ubyte *scanline = FreeImage_GetScanLine(bitmap, y);
for(int x = 0; x < width; x++) {
//This magic has to happen with the y-coordinate in order to keep FreeImage from following its default behavior, and generating
//the image upside down.
ulong i = cast(ulong)(((height - 1) - y) * width + x);
uint v = cast(uint)(data[i] * 0xFFFFFFFF);
ubyte[4] bytes = nativeToLittleEndian(v);
scanline[x * uint.sizeof + 0] = bytes[0];
scanline[x * uint.sizeof + 1] = bytes[1];
scanline[x * uint.sizeof + 2] = bytes[2];
scanline[x * uint.sizeof + 3] = bytes[3];
}
}
//DEBUG
ulong index = cast(ulong)(xtest * uint.sizeof);
writeln("Final: ", FreeImage_GetScanLine(bitmap, ytest)
[index .. index + uint.sizeof]);
FreeImage_Save(FIF_TIFF, bitmap, fileName.toStringz);
FreeImage_Unload(bitmap);
}
Мне еще не удалось найти программу, созданную кем-либо еще, которая легко визуализировала бы 32-битное изображение в оттенках серого на доступной палитре монитора. Однако я оставил свой проверочный код, который будет последовательно записывать один и тот же массив как в верхний, так и в нижний массив DEBUG, и для меня этого достаточно.
Надеюсь, это поможет кому-то другому в будущем.