// -----------------------------------------------------
// Функции для работы с файлами в формате DIB
// -----------------------------------------------------

#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <commdlg.h>
#include <mem.h>
#pragma hdrstop

#include "dib.hpp"

// -------------------------------
// Функция DIBSelectFile
// Выбор DIB-файла
// -------------------------------

HFILE DIBSelectFile(void)
{
  OPENFILENAME ofn;
  char szFile[256];
  char szFileTitle[256];
  char szFilter[256] =
	 "Bitmap Files\0*.bmp;*.dib;*.rle\0Any Files\0*.*\0";
  HFILE hf;

  szFile[0] = '\0';

  memset(&ofn, 0, sizeof(OPENFILENAME));
  ofn.lStructSize       = sizeof(OPENFILENAME);
  ofn.hwndOwner         = NULL;
  ofn.lpstrFilter       = szFilter;
  ofn.nFilterIndex      = 1;
  ofn.lpstrFile         = szFile;
  ofn.nMaxFile          = sizeof(szFile);
  ofn.lpstrFileTitle    = szFileTitle;
  ofn.nMaxFileTitle     = sizeof(szFileTitle);
  ofn.lpstrInitialDir   = NULL;
  ofn.Flags =   OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST
				  | OFN_HIDEREADONLY;

  if (GetOpenFileName(&ofn)) {
    // Открываем выбранный файл
    hf = _lopen(ofn.lpstrFile, OF_READ);
    // Возвращаем идентификатор файла
	 return hf;
  }
  // При отказе от выбора возвращаем
  // нулевое значение
  else return 0;
}

// -------------------------------
// Функция DIBReadFile
// Чтение DIB-файла
// -------------------------------

HDIB DIBReadFile(HFILE hfDIBFile, DWORD *dwFileSize)
{
  // Идентификатор глобального блока
  // памяти, который будет использован для
  // чтения файла
  HDIB hDib;

  HCURSOR  hCursor; // идентификатор курсора
  
  // Указатель на глобальный блок памяти
  LPDIB lpBuf;

  // Курсор в виде песочных часов
  hCursor = SetCursor (LoadCursor(NULL, IDC_WAIT));

  // Определяем размер файла. Для этого
  // устанавливаем текущую позицию на
  // конец файла
  *dwFileSize = _llseek(hfDIBFile, 0l, 2);

  // Устанавливаем текущую позицию
  // на начало файла
  _llseek(hfDIBFile, 0l, 0);

  // Заказываем глобальный блок памяти,
  // размер которого равен длине файла
  hDib = (HDIB)GlobalAlloc(GMEM_FIXED, *dwFileSize);
  lpBuf = (unsigned char huge *)GlobalLock(hDib);

  // Если мало свободной памяти,
  // возвращаем признак ошибки
  if(lpBuf == NULL) return(NULL);

  // Читаем файл в полученный блок памяти
  _hread(hfDIBFile, lpBuf, *dwFileSize);

  // Восстанавливаем курсор
  SetCursor (hCursor);

  // Расфиксируем память
  GlobalUnlock(hDib);

  // Закрываем файл
  _lclose(hfDIBFile);

  return hDib;
}

// -------------------------------
// Функция DIBInfo
// Вывод диалоговой панели с информацией
// о DIB-файле
// -------------------------------

BOOL DIBInfo(HDIB hDib, DWORD dwFileSize)
{
  char szBuf[256], szBuf1[256];
  DWORD biSize;
  LPBITMAPFILEHEADER lpDIBFileHeader;
  LPBITMAPINFOHEADER lpDIBInfoHeader;
  LPBITMAPCOREHEADER lpDIBCoreHeader;
  DWORD bfOffset;
  BOOL bWDIB;
  LPDIB lpDIBPtr;
  int nDIBType;
  WORD wNumColors;

  // Определяем тип битового изображения
  nDIBType = DIBType(hDib);

  if(!nDIBType) // если ошибка, выдаем сообщение
  {
    MessageBox(NULL, "Ошибка в формате DIB-файла",
      "Bitmap Info", MB_OK | MB_ICONHAND);
    return FALSE;
  }

  // Фиксируем область памяти, в которую загружен DIB
  lpDIBPtr = (unsigned char huge *)GlobalLock(hDib);
  if(lpDIBPtr == NULL)
     return(FALSE);

  lpDIBFileHeader = (LPBITMAPFILEHEADER)lpDIBPtr;

  // Определяем смещение бит изображения
  // и размер заголовка
  bfOffset = lpDIBFileHeader->bfOffBits;
  biSize = (DWORD)(lpDIBPtr[sizeof(BITMAPFILEHEADER)]);

  // Готовим текстовую строку для вывода
  wsprintf(szBuf,  "Размер заголовка, байт:\t%ld\n", biSize);
  wsprintf(szBuf1, "Размер файла, байт:    \t%ld\n", dwFileSize);
  lstrcat(szBuf, szBuf1);
  wsprintf(szBuf1, "Смещение изображения, байт:\t%ld\n", bfOffset);
  lstrcat(szBuf, szBuf1);

  // В зависимости от формата DIB (PM или Windows)
  // выводим различную информацию из заголовка файла
  if((nDIBType == WINRGB_DIB) ||
     (nDIBType == WINRLE4_DIB) ||
     (nDIBType == WINRLE8_DIB))
  {
    wsprintf(szBuf1,  "\nBMP для Windows\n", biSize);
    lstrcat(szBuf, szBuf1);
    lpDIBInfoHeader =
      (LPBITMAPINFOHEADER)(lpDIBPtr + sizeof(BITMAPFILEHEADER));

    wsprintf(szBuf1, "Размер:\t%ldx%ld\n",
      lpDIBInfoHeader->biWidth, lpDIBInfoHeader->biHeight);
    lstrcat(szBuf, szBuf1);

    wsprintf(szBuf1, "Бит на пиксел:\t%d\n",
      lpDIBInfoHeader->biBitCount);
    lstrcat(szBuf, szBuf1);

    wNumColors = DIBNumColors(lpDIBPtr);
    wsprintf(szBuf1, "Таблица цветов:\t%d\n", wNumColors);
    lstrcat(szBuf, szBuf1);

    if(lpDIBInfoHeader->biCompression == BI_RGB)
    {
      lstrcat(szBuf, "Без компрессии\n");
    }
    else if(lpDIBInfoHeader->biCompression == BI_RLE4)
    {
      lstrcat(szBuf, "Компрессия RLE4\n");
    }
    else if(lpDIBInfoHeader->biCompression == BI_RLE8)
    {
      lstrcat(szBuf, "Компрессия RLE8\n");
    }
  }

  // Для файлов DIB в формате PM
  else
  {
    wsprintf(szBuf1,
      "\nBMP для Presentation Manager\n", biSize);
    lstrcat(szBuf, szBuf1);
    lpDIBCoreHeader =
      (LPBITMAPCOREHEADER)(lpDIBPtr + sizeof(BITMAPFILEHEADER));

    wsprintf(szBuf1, "Размер:\t%dx%d\n",
      lpDIBCoreHeader->bcWidth, lpDIBCoreHeader->bcHeight);
    lstrcat(szBuf, szBuf1);

    wsprintf(szBuf1, "Бит на пиксел:\t%d\n",
      lpDIBCoreHeader->bcBitCount);
    lstrcat(szBuf, szBuf1);
  }

  MessageBox(NULL, (LPSTR)szBuf,
    "Bitmap Info", MB_OK | MB_ICONINFORMATION);

  GlobalUnlock(hDib);
  return TRUE;
}


// -------------------------------
// Функция DIBType
// Опеределение и проверка формата DIB
// -------------------------------

int DIBType(HDIB hDib)
{
  LPBITMAPFILEHEADER lpDIBFileHeader;
  LPBITMAPINFOHEADER lpih;
  LPBITMAPCOREHEADER lpch;

  DWORD biSize;
  LPDIB hDIBPtr;
  int   nDIBType;

  if(hDib == NULL)
    // Неправильный идентификатор DIB
    return(-2); 

  // Фиксируем память, в которой находится DIB
  hDIBPtr = (LPDIB)GlobalLock(hDib);
  if(hDIBPtr == NULL)
    return(-1);

  lpDIBFileHeader = (LPBITMAPFILEHEADER)hDIBPtr;

  // Проверяем тип файла
  if(lpDIBFileHeader->bfType != 0x4d42)
  {
    GlobalUnlock(hDib);
    return 0;
  }

  // Проверяем размер заголовка
  biSize = (DWORD)(hDIBPtr[sizeof(BITMAPFILEHEADER)]);

  if(biSize == sizeof(BITMAPINFOHEADER)) // 40 байт
  {
    // Это заголовок DIB в формате Windows
    lpih =
      (LPBITMAPINFOHEADER)(hDIBPtr + sizeof(BITMAPFILEHEADER));

    // Проверяем основные поля заголовка DIB
    if((lpih->biPlanes   == 1) &&
       ((lpih->biBitCount == 1) ||
        (lpih->biBitCount == 4) ||
        (lpih->biBitCount == 8) ||
        (lpih->biBitCount == 24)) &&
        ((lpih->biCompression  == BI_RGB) ||
         (lpih->biCompression == BI_RLE4 && lpih->biBitCount == 4) ||
         (lpih->biCompression == BI_RLE8 && lpih->biBitCount == 8)))
    {
       // Определяем метод компрессии файла
       if(lpih->biCompression  == BI_RGB)
         nDIBType = WINRGB_DIB;
       else if(lpih->biCompression  == BI_RLE4)
         nDIBType = WINRLE4_DIB;
       else if(lpih->biCompression  == BI_RLE8)
         nDIBType = WINRLE8_DIB;
       else
         nDIBType = 0;
    }
    else
       nDIBType = 0;
  }

  else if(biSize == sizeof(BITMAPCOREHEADER)) // 12 байт
  {
    // Это заголовок DIB в формате Presentation Manager
    lpch =
      (LPBITMAPCOREHEADER)(hDIBPtr + sizeof(BITMAPFILEHEADER));

    // Проверяем основные поля заголовка DIB
    if((lpch->bcPlanes   == 1) &&
       (lpch->bcBitCount == 1 ||
        lpch->bcBitCount == 4 ||
        lpch->bcBitCount == 8 ||
        lpch->bcBitCount == 24))
    {
      nDIBType = PM_DIB;
    }
    else
      nDIBType = 0;
  }

  else
    nDIBType = 0;

  GlobalUnlock(hDib);

  // Возвращаем тип файла или признак ошибки
  return nDIBType;
}

// -------------------------------
// Функция DIBNumColors
// Определение размера палитры
// -------------------------------

WORD DIBNumColors(LPDIB lpDib)
{
  DWORD dwColorUsed;
  LPBITMAPINFOHEADER lpih;

  lpih =
    (LPBITMAPINFOHEADER)(lpDib + sizeof(BITMAPFILEHEADER));

  // Количество цветов
  dwColorUsed = lpih->biClrUsed;

  // Если используется палитра уменьшенного размера,
  // возвращаем нужный размер
  if(dwColorUsed)
    return((WORD)dwColorUsed);

  // Если количество использованных цветов не указано,
  // вычисляем стандартный размер палитры исходя из
  // количества бит, определяющих цвет пиксела
  switch(lpih->biBitCount)
  {
    case 1:
      return 2;
    case 4:
      return 16;
    case 8:
      return 256;
    default:
      return 0;   // палитра не используется
  }
}

// -------------------------------
// Функция DIBHeight
// Определение высоты DIB в пикселах
// -------------------------------

WORD DIBHeight(LPDIB lpDib)
{
  LPBITMAPINFOHEADER lpih;

  lpih = (LPBITMAPINFOHEADER)(lpDib
    + sizeof(BITMAPFILEHEADER));
  return lpih->biHeight;
}

// -------------------------------
// Функция DIBWidth
// Определение ширины DIB в пикселах
// -------------------------------

WORD DIBWidth(LPDIB lpDib)
{
  LPBITMAPINFOHEADER lpih;

  lpih = (LPBITMAPINFOHEADER)(lpDib
    + sizeof(BITMAPFILEHEADER));
  return lpih->biWidth;
}

// -------------------------------
// Функция DIBFindBits
// Определение адреса массива бит изображения
// -------------------------------

LPSTR DIBFindBits(LPDIB lpDib)
{
  LPBITMAPFILEHEADER lpfh;
  LPBITMAPINFOHEADER lpih;

  lpfh = (LPBITMAPFILEHEADER)lpDib;

  // Используем значение, указанное в заголовке
  // файла (если оно не равно нулю)
  if(lpfh->bfOffBits)
    return((LPSTR)lpfh + lpfh->bfOffBits);

  // Вычисляем адрес исходя из размеров заголовков и
  // таблицы цветов
  lpih =
    (LPBITMAPINFOHEADER)(lpDib + sizeof(BITMAPFILEHEADER));

  return((LPSTR)lpih + lpih->biSize +
    (DWORD)(DIBNumColors(lpDib) * sizeof(RGBQUAD)));
}

// -------------------------------
// Функция DIBPaint
// Рисование DIB при помощи функции StretchDIBits
// -------------------------------

BOOL DIBPaint(HDC hdc, int x, int y, HDIB hDib)
{
  HBITMAP hbmp;
  HDC hMemDC;
  WORD wHeight, wWidth;
  LPDIB lpDib;
  LPBITMAPINFOHEADER lpih;

  lpDib = (LPDIB)GlobalLock(hDib);
  if(lpDib == NULL)
    return(-1);

  lpih =
    (LPBITMAPINFOHEADER)(lpDib + sizeof(BITMAPFILEHEADER));

  // Определяем размеры DIB 
  wHeight = lpih->biHeight;
  wWidth  = lpih->biWidth;

  // Рисуем DIB без масштабирования
  StretchDIBits(hdc,
     x, y, wWidth, wHeight,
     0, 0, wWidth, wHeight,
     DIBFindBits(lpDib),
     (LPBITMAPINFO)lpih,
     DIB_RGB_COLORS, SRCCOPY);

  GlobalUnlock(hDib);

  return TRUE;
}

// -------------------------------
// Функция DIBPaintBlt
// Рисование DIB при помощи функции BitBlt
// -------------------------------

BOOL DIBPaintBlt(HDC hdc, int x, int y, HDIB hDib)
{
  HBITMAP hbmp;
  HDC hMemDC;
  WORD wHeight, wWidth;
  LPDIB lpDib;
  LPBITMAPINFOHEADER lpih;


  lpDib = (LPDIB)GlobalLock(hDib);
  if(lpDib == NULL)
    return(-1);

  lpih = (LPBITMAPINFOHEADER)(lpDib + sizeof(BITMAPFILEHEADER));

  wHeight = lpih->biHeight;
  wWidth  = lpih->biWidth;

  // Создаем совместимое битовое изображение
  hbmp = CreateCompatibleBitmap(hdc, wWidth, wHeight);

  // Создаем совместимый контекст памяти
  hMemDC = CreateCompatibleDC(hdc);

  // Преобразуем DIB в DDB
  SetDIBits(hdc, hbmp, 0, wHeight,
    DIBFindBits(lpDib),
    (LPBITMAPINFO)lpih, DIB_RGB_COLORS);

  // Выбираем DDB в контекст отображения
  hbmp = (HBITMAP)SelectObject(hMemDC, hbmp);

  // Рисуем DDB
  BitBlt(hdc, x, y, wWidth, wHeight,
         hMemDC, 0, 0, SRCCOPY);

  // Удаляем контекст памяти
  DeleteObject(SelectObject(hMemDC, hbmp));
  DeleteDC(hMemDC);

  GlobalUnlock(hDib);
  return TRUE;
}

// -------------------------------
// Функция DIBCreatePalette
// Создаем палитру на базе таблицы цветов DIB
// -------------------------------

HPALETTE DIBCreatePalette(HDIB hDib)
{
   LPLOGPALETTE lpPal;
   HPALETTE     hPal = NULL;
   HANDLE       hLogPal;
   int i, wNumColors;
   LPSTR lpbi;
   LPBITMAPINFO lpbmi;

   if (!hDib)
      return NULL;

   lpbi = (LPSTR)GlobalLock(hDib);
   lpbmi =
     (LPBITMAPINFO)(lpbi + sizeof(BITMAPFILEHEADER));

   // Определяем размер таблицы цветов
   wNumColors = DIBNumColors(lpbi);

   // Если в DIB есть таблица цветов, создаем палитру
   if (wNumColors)
   {
      // Заказываем память для палитры
      hLogPal = GlobalAlloc(GHND, sizeof(LOGPALETTE)
         + sizeof(PALETTEENTRY) *  wNumColors);

      if (!hLogPal)
      {
        GlobalUnlock(hDib);
        return NULL;
      }

      // Получаем указатель на палитру
      lpPal = (LPLOGPALETTE)GlobalLock(hLogPal);

      // Заполняем заголовок
      lpPal->palVersion = 0x300;
      lpPal->palNumEntries = wNumColors;

      // Заполняем палитру
      for (i = 0; i < wNumColors; i++)
      {
        lpPal->palPalEntry[i].peRed   =
           lpbmi->bmiColors[i].rgbRed;

        lpPal->palPalEntry[i].peGreen =
           lpbmi->bmiColors[i].rgbGreen;

        lpPal->palPalEntry[i].peBlue  =
           lpbmi->bmiColors[i].rgbBlue;

        lpPal->palPalEntry[i].peFlags = 0;
      }

      // Создаем палитру
      hPal = CreatePalette(lpPal);
      if (!hPal)
      {
        GlobalUnlock(hLogPal);
        GlobalFree(hLogPal);
        return NULL;
      }

     GlobalUnlock(hLogPal);
     GlobalFree(hLogPal);
   }

   GlobalUnlock(hDib);

   // Возвращаем идентификатор созданной палитры 
   return hPal;
}

// -------------------------------
// Функция DIBGetBmpRect
// -------------------------------

BOOL DIBGetBmpRect(HDIB hDib, POINT *pt)
{
  LPDIB lpDIB;
  LPBITMAPINFOHEADER lpih;

  // Фиксируем область памяти, в которую загружен DIB
  lpDIB = (unsigned char huge *)GlobalLock(hDib);
  if(lpDIB == NULL)
     return(FALSE);

  lpih = (LPBITMAPINFOHEADER)(lpDIB + sizeof(BITMAPFILEHEADER));

  pt->y = lpih->biHeight;
  pt->x = lpih->biWidth;

  GlobalUnlock(hDib);

  return TRUE;
}

// -------------------------------
// Функция SizeToHiMetric
// Преобразование ширины и высоты для
// режима отображения MM_HIMETRIC
// -------------------------------
static void
SizeToHiMetric(int *width, int *height)
{
  HDC hDC = GetDC(0);

  if(hDC)
  {
    // Определяем количество пикселов на один
    // логический дюйм по горизонтали и вертикали
    int dpiX = GetDeviceCaps (hDC, LOGPIXELSX);
    int dpiY = GetDeviceCaps (hDC, LOGPIXELSY);

    // Константа для пересчета
    const long  HiMetricPerInch = 2540;

    // Выполняем пересчет
    if(width) *width = int (*width * HiMetricPerInch / dpiX);
    if(height) *height = int (*height * HiMetricPerInch / dpiY);

    ReleaseDC(0, hDC);
  }
}

// -------------------------------
// Функция DIBCopyToClipboard
// Копирование DIB в Clipboard
// -------------------------------
BOOL DIBCopyToClipboard(HDIB hDib, HWND hwnd)
{
  POINT ptSize;
  HDC hdcMeta, hdc, hdcMem;
  HMETAFILE hMF;
  HBITMAP hBitmap;
  HPALETTE hPal, hOldPal;
  HGLOBAL hMeta;
  LPMETAFILEPICT lpMeta;
  int x, y;

  // Если DIB не загружен или он имеет тип,
  // отличный от WINRGB_DIB, копирование не выполняем
  if(hDib == NULL) return FALSE;
  if(DIBType(hDib) != WINRGB_DIB) return FALSE;

  // Открываем и очищаем Clipboard
  if(!OpenClipboard(hwnd)) return FALSE;
  EmptyClipboard();

  // Определяем размеры DIB
  DIBGetBmpRect(hDib, &ptSize);
  x = ptSize.x;
  y = ptSize.y;

  // Создаем DDB
  // Копируем в него содержимое окна,
  // размеры копируемой области равны
  // размерам DIB
  hdc = GetDC(hwnd);
  hdcMem = CreateCompatibleDC(hdc);
  hBitmap = CreateCompatibleBitmap(hdc, x, y);
  if(hBitmap)
  {
    SelectObject(hdcMem, hBitmap);
    StretchBlt(hdcMem, 0, 0, x, y,
       hdc, 0, 0, x, y, SRCCOPY);
  }
  DeleteDC(hdcMem);
  ReleaseDC(hwnd, hdc);

  // Создаем метафайл в памяти
  hdcMeta = CreateMetaFile((LPSTR)NULL);

  // Устанавливаем начало координат и размеры изображения
  SetWindowOrgEx(hdcMeta, 0, 0, NULL);
  SetWindowExtEx(hdcMeta, ptSize.x, ptSize.y, NULL);

  // Создаем палитру из DIB
  hPal = DIBCreatePalette(hDib);
  if(hPal)
  {
    // Выбираем и реализуем палитру в контекст
    // метафайла
    hOldPal = SelectPalette(hdcMeta, hPal, FALSE);
    RealizePalette(hdcMeta);
  }

  // Рисуем DIB в контексте метафайла
  DIBPaint(hdcMeta, 0, 0, hDib);

  // Выбираем старую палитру
  if(hPal)
    SelectPalette(hdcMeta, hOldPal, FALSE);

  // Закрываем метафайл
  hMF = CloseMetaFile(hdcMeta);

  // Заказываем память для заголовка метафайла
  hMeta = GlobalAlloc(GHND, sizeof(METAFILEPICT));
  lpMeta = (LPMETAFILEPICT)GlobalLock(hMeta);

  // Преобразуем координаты для
  // режима отображения MM_HIMETRIC
  SizeToHiMetric (&x, &y);

  // Заполняем заголовок метафайла
  lpMeta->mm = MM_ANISOTROPIC;
  lpMeta->xExt = x;
  lpMeta->yExt = y;
  lpMeta->hMF = hMF;

  // Расфиксируем память заголовка метафайла
  GlobalUnlock(hMeta);

  // Записываем метафайл в Clipboard
  if(hMeta)
    SetClipboardData(CF_METAFILEPICT, hMeta);

  // Записываем DDB в Clipboard
  if(hBitmap)
    SetClipboardData(CF_BITMAP, hBitmap);

  // Записываем палитру в Clipboard
  if(hPal)
    SetClipboardData(CF_PALETTE, hPal);

  // Закрываем Clipboard
  CloseClipboard();
  return TRUE;
}

