Сохраняем CF_DIB\CF_DIBV5 в файл
August 27th, 2010 Begemot Posted in Программирование
Казалось бы простое дело, а убил большую часть дня на это. И то сделал только благодаря помощи одного человека, автора программы для работы с битмапами, иконками и т.д. Приятно разговаривать с человеком который хорошо разбирается в теме:) В итоге узнал очень много сокровенных знаний, которым и буду делится.
Итак у нас есть данные в памяти в форматах CF_DIB/CF_DIBV5 (да я знаю что это форматы буфера обмена!) ну или другими словами у нас есть BITMAPINFO. А мы хотим получить wxBitmap.
Сначала нам нужно получить HBITMAP, который потом уже можно конвертить в wxBitmap используя wxBitmap::SetHBITMAP(да в документации ее нету).
Много писать мне уже сегодня лень, поэтому просто покажу код, там в комментариях описаны все проблемы и решения.
{
/* там с клипбоардом вообще мрак, MS сам свои спецификации не соблюдает, я тебе уже жаловался, когда в классы всё это барахло заворачивал
когда в biCompression указано BI_BITFIELDS за структурой следуют 12 байт маски цветовых каналов, надо на 12 байт смещать начало пиксельного массива
1) 12 байт пропускать надо? только если biCompression==BI_BITFIELDS
2) по-хорошему, их надо не пропускать, а анализировать и использовать
3) работает ли твой код в случае с 8-битным битмапом (т.е. таким, который включает палитру)?
в третьем с конца - нужно пропустить кол-во элементов палитры
так, а как узнать количество элементов палитры?
biClrUsed или (1<<biBitCount), если biClrUsed=0 и это делать только если biBitCount <= 8
*/
int offset = 0;
if (bi->bmiHeader.biBitCount <= 8)
{
offset = bi->bmiHeader.biClrUsed ? bi->bmiHeader.biClrUsed : (1 << bi->bmiHeader.biBitCount);
}
else
{
if (bi->bmiHeader.biCompression == BI_BITFIELDS) offset = 3;
}
/*
Правильно использовать independing bitmap - но похоже тут у wxBitmap проблемы, мы получаем правильный hBitmap (видно на экране), но в файле пробелмы - на определенной
цветности\форматах (тестировалось только для DIB)
поэтому использую DDB - тут теряется цветность, итоговая цветность не равна оригинальной, а зависит от текущих настроек экрана... но зато работает.
// (RGBQUAD*)((byte*)bi + bi->bmiHeader.biSize) + offset instead of &bi->bmiColors + shift - for support both versions DIB\DIBV5
*/
#if 0
// using independent bitmap
char * pBits;
HBITMAP hBitmap = CreateDIBSection(CreateCompatibleDC(NULL), bi, DIB_RGB_COLORS, (void**)&pBits, NULL, 0);
memcpy(pBits, (RGBQUAD*)((byte*)bi + bi->bmiHeader.biSize) + offset, abs(bi->bmiHeader.biHeight) * (((bi->bmiHeader.biWidth * bi->bmiHeader.biBitCount + 31) & ~31) / 8));
#else // use DDB (dependent) - we lost information about color depth - bad way
HBITMAP hBitmap = CreateDIBitmap(GetDC(NULL), &bi->bmiHeader, CBM_INIT, (RGBQUAD*)((byte*)bi + bi->bmiHeader.biSize) + offset, bi, DIB_RGB_COLORS);
#endif
// //------------------------------------------------------------------------------------------------------------------
// { // write to screen to test it
// HDC screen = GetDC(NULL);
// HDC bitmap = CreateCompatibleDC(screen);
// HBITMAP old = (HBITMAP)SelectObject(bitmap, hBitmap);
// int w = 0, h = 0, d = 0;
// w = bi->bmiHeader.biWidth; h = bi->bmiHeader.biHeight; d = bi->bmiHeader.biBitCount;
// BitBlt(screen, 100, 100, w, h, bitmap, 0, 0, SRCCOPY);
// SelectObject(bitmap, old);
// DeleteDC(bitmap);
// ReleaseDC(NULL, screen);
// }
// //------------------------------------------------------------------------------------------------------------------
wxBitmap bitmap;
bitmap.SetHBITMAP(hBitmap);
if (bitmap.IsOk())
{
bool res = bitmap.SaveFile(file, BegUtils::DetermineImageType(file));
wxASSERT(res);
}
else wxASSERT(0);
}
Да p->data.GetData() может содержать либо просто DIB либо 5ю версию.
Если кто знает другое решение, которое позволит обойти проблему и сохранять данные корректно, или кто пофиксит баг в wxBitmap или хотя бы сформулирует и закинет в трекер, пишите:)
August 28th, 2010 at 2:40 pm
Всё уже придумано до нас.
Я тебе уже говорил – глянь на исходники вх-ов:
\src\msw\ole\dataobj.cpp
[code]
const BITMAPINFO * const pbmi = (const BITMAPINFO *)buf;
HBITMAP hbmp = wxDIB::ConvertToBitmap(pbmi);
wxCHECK_MSG( hbmp, FALSE, wxT("pasting/dropping invalid bitmap") );
const BITMAPINFOHEADER * const pbmih = &pbmi->bmiHeader;
wxBitmap bitmap(pbmih->biWidth, pbmih->biHeight, pbmih->biBitCount);
bitmap.SetHBITMAP((WXHBITMAP)hbmp);
[/code]
August 28th, 2010 at 3:05 pm
А в отличных от msw платформах это будиет работать?
August 28th, 2010 at 3:39 pm
Boris, конечно нет. HBITMAP – это чисто виндовая фишка.
August 28th, 2010 at 10:21 pm
Вот блин. Ты этого не говорил, ок завтра протестю как оно в работе.
August 29th, 2010 at 12:39 am
ну да, конкретно на эту реализацию я тебе не указывал.
А про класс wxDIB я тебе говорил, но ты почему-то не захотел на него смотреть – в вх-ах есть много полезных недокументированных функций.
August 29th, 2010 at 10:29 am
Посмотрел я этот пример, в принципе работает, делает тоже самое что и мой код CreateDIBitmap – соответвенно тот же недостаток – искажение информации о цветности:(
К тому же мой код работает как с DIB так и с DIBV5 и подозреваю с другими версиями тоже. Вариант из \src\msw\ole\dataobj.cpp работает только с DIB (они там жестко привязываются к размеру структуры заголовка, а я беру реальное значение из его поля)
И еще там немного по другому считается смещение, но я пока незнаю кто считает лучше, надеюсь подскажут 🙂