Сохраняем CF_DIB\CF_DIBV5 в файл

August 27th, 2010 Begemot Posted in Программирование

Казалось бы простое дело, а убил большую часть дня на это. И то сделал только благодаря помощи одного человека, автора программы для работы с битмапами, иконками и т.д. Приятно разговаривать с человеком который хорошо разбирается в теме:) В итоге узнал очень много сокровенных знаний, которым и буду делится.

Итак у нас есть данные в памяти в форматах CF_DIB/CF_DIBV5 (да я знаю что это форматы буфера обмена!) ну или другими словами у нас есть BITMAPINFO. А мы хотим получить wxBitmap.

Сначала нам нужно получить HBITMAP, который потом уже можно конвертить в wxBitmap используя wxBitmap::SetHBITMAP(да в документации ее нету).

Много писать мне уже сегодня лень, поэтому просто покажу код, там в комментариях описаны все проблемы и решения.

 

        if (BITMAPINFO * bi = static_cast<BITMAPINFO*>(p->data.GetData()))
        {
              /*   там с клипбоардом вообще мрак, 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 или хотя бы сформулирует и закинет в трекер, пишите:)


Если пост полезен для вас вы можете подписаться на RSS или мы можем доставлять вам новые посты прямо в ваш почтовый ящик.

Related:

6 Responses to “Сохраняем CF_DIB\CF_DIBV5 в файл”

  1. Всё уже придумано до нас.
    Я тебе уже говорил – глянь на исходники вх-ов:
    \src\msw\ole\dataobj.cpp

        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);
    
  2. А в отличных от msw платформах это будиет работать?

  3. Boris, конечно нет. HBITMAP – это чисто виндовая фишка.

  4. Вот блин. Ты этого не говорил, ок завтра протестю как оно в работе.

  5. ну да, конкретно на эту реализацию я тебе не указывал.
    А про класс wxDIB я тебе говорил, но ты почему-то не захотел на него смотреть – в вх-ах есть много полезных недокументированных функций.

  6. Посмотрел я этот пример, в принципе работает, делает тоже самое что и мой код CreateDIBitmap – соответвенно тот же недостаток – искажение информации о цветности:(

    К тому же мой код работает как с DIB так и с DIBV5 и подозреваю с другими версиями тоже. Вариант из \src\msw\ole\dataobj.cpp работает только с DIB (они там жестко привязываются к размеру структуры заголовка, а я беру реальное значение из его поля)

    И еще там немного по другому считается смещение, но я пока незнаю кто считает лучше, надеюсь подскажут 🙂

Leave a Reply