Loading Jpegs

Can anyone point me to a good tutorial on loading jpg's, png's, tif's, etc? I have been studying bitmaps and BLT functions. But, I can't seem to find any good info on loading files with extensions other than ".bmp". Do I need to add library files, special header files, etc? I am using Visual Studio Community 2019.
The stb_image library is relatively easy to use.
https://github.com/nothings/stb

Look at stb_image.h

It supports:
JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib)
PNG 1/2/4/8/16-bit-per-channel
TGA (not sure what subset, if a subset)
BMP non-1bpp, non-RLE
PSD (composited view only, no extra channels, 8/16 bit-per-channel)
GIF (*comp always reports as 4-channel)
HDR (radiance rgbE format)
PIC (Softimage PIC)
PNM (PPM and PGM binary only)
I guess it doesn't support TIF?

Here's a tutorial on it: https://solarianprogrammer.com/2019/06/10/c-programming-reading-writing-images-stb_image-libraries/
(You can ignore the git stuff, just download the header)

It's a C library, but works with C++, too.
Another example of it being used: https://github.com/SFML/SFML/blob/489482a630c5286e488ffc270c10562809056d8e/src/SFML/Graphics/ImageLoader.cpp

There is also LodePNG, also pretty easy to use, but it's only for PNG.
https://lodev.org/lodepng/

That site also has examples.
Last edited on
You could use the Win API.
https://docs.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-introduction-to-gdi--about
https://www.codeproject.com/Articles/1160/Doodle-A-Basic-Paint-Package-in-GDIplus

If you just want to display some info about a bitmap:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <Windows.h>
#include <gdiplus.h> // add Gdiplus.lib

int main()
{
  ULONG_PTR m_gdiplusToken;
  Gdiplus::GdiplusStartupInput gdiplusStartupInput;
  Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, nullptr);

  Gdiplus::Image img(L"C:\\Temp\\Demo.png");
  std::cout << "Width: " << img.GetWidth() << " px\n";
  std::cout << "Height: " << img.GetHeight() << " px\n";

  Gdiplus::GdiplusShutdown(m_gdiplusToken);
}

You got 2 different suggestions, but if you don't want you neither have to use 3rd party libs or GDI functions.

There is WIC API:
https://docs.microsoft.com/en-us/windows/win32/wic/-wic-api
Thanks all. That is a lot to digest. For the moment, I am looking for the most practical way to load a "JPG" or "PNG" image, and manipulate it with Windows BLT functions. For example, how would I use the GDI+ code above, to get a handle to the image bitmap? Also, I'm a bit confused by the calling convention in line 11. Could anyone explain?
You get the handle with GetHBITMAP.
However the easier way is to create a Graphics object and use this to draw the bitmap.

Take some time to browse the doc.
Well, I have been trying to use the MS documentation to assemble a simple image-load program. There seems to be no consistency to the procedures. Anyway, here is the code that I put together. I get nothing but errors. What am I doing wrong? I seem to be way off:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <windows.h>
#include <gdiplus.h>

#define NUMSEGS  1000
#define PI       3.141592654
#define TWOPI    6.283185307
#define PIHALF   1.570796327

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
    const wchar_t szAppName[] = L"LoadImage1";
    const wchar_t szAppTitle[] = L"Load Image Test 1";
    HWND        hwnd;
    MSG         msg;
    WNDCLASSEX  wndclass;
    ULONG_PTR   gdiplusToken;

    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    wndclass.cbSize = sizeof(wndclass);
    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(0, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor(0, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = 0;
    wndclass.lpszClassName = szAppName;
    wndclass.hIconSm = LoadIcon(0, IDI_APPLICATION);

    RegisterClassEx(&wndclass);

    hwnd = CreateWindow(szAppName, szAppTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, 0);

    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, 0, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    Gdiplus::GdiplusShutdown(gdiplusToken);
    return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
    static int   cxClient, cyClient;
    HDC          hdc;
    PAINTSTRUCT  ps;
    

    switch (iMsg)
    {
    case WM_SIZE:
        cxClient = LOWORD(lParam);
        cyClient = HIWORD(lParam);
        return 0;

    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);

        Gdiplus::Graphics graphic1(hdc);
        Gdiplus::Image image1(L"Night BW phone.jpg");
        graphic1.DrawImage(image1, 10, 10);

        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, iMsg, wParam, lParam);
}


Here are the errors:

Line 59: transfer of control bypasses initialization of:

Line 71: no instance of overloaded function "Gdiplus::Graphics::DrawImage" matches the argument list

Line 71: 'Gdiplus::Status Gdiplus::Graphics::DrawImage(Gdiplus::Image *,const Gdiplus::Point *,INT,INT,INT,INT,INT,Gdiplus::Unit,const Gdiplus::ImageAttributes *,Gdiplus::DrawImageAbort,void *)': cannot convert argument 1 from 'Gdiplus::Image' to 'Gdiplus::Image *'

Line 76: initialization of 'image1' is skipped by 'case' label


As a final note, I am pretty rusty at working with classes. So, the errors could be in my notoation.
Because of a "peculiarity" with initializing variables in a switch case enclose the code in a code block:
66
67
68
69
70
71
72
73
74
75
76
    case WM_PAINT:
     {
        hdc = BeginPaint(hwnd, &ps);

        Gdiplus::Graphics graphic1(hdc);
        Gdiplus::Image image1(L"Night BW phone.jpg");
        graphic1.DrawImage(&image1, 10, 10);

        EndPaint(hwnd, &ps);
        return 0;
     }

Also, Image::DrawImage requires you pass the address of the image. Please note the address-of(&) operator on line 72.
Thanks Furry Guy. That worked! I figured that it had to be something simple.

Also, I found that this line is required:

 
#pragma comment (lib,"Gdiplus.lib") 
That code line is needed only if you don't want to add the lib using the project's property pages. The linker does need to know about the lib file somehow.
Now that you've enclosed the WM_PAINT case in a code block you no longer need to define HDC and PAINTSTRUCT at the top of your WndProc function, you can move the definitions to the WM_PAINT case. As long as you don't need them in any other case.

BTW, about your #defines of a handful of constant variable names. Don't. Use const/constexpr variables instead when compiling as C++. #define as you should know is a straight text replacement by the preprocessor and using #defines in C++ code can lead to type error bugs. As well as make it hard/impossible to trace in a debugger.

#define NUMSEGS 1000
could become
const int NUMSEGS { 1000 };
Using uniform initialization, of course. ;)

Of course this presumes you are compiling the source as C++, not C.

Another thing to note....LoadCursor/LoadIcon are deprecated Win32 functions. Use LoadImage instead:
1
2
3
   wc.hIcon   = (HICON)   LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR);
   wc.hIconSm = (HICON)   LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR);
   wc.hCursor = (HCURSOR) LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);

I won't go as far as using C++ casts, that is far too much ugliness even for Win32 API code. :D
Thanks Furry Guy for the tips. Almost all of the documentation and tutorials I find, seem dated. Even most of MS's documentation seems dated back to Win98.

Is it true that the use of "NULL" is also now deprecated? I read somewhere that '0' (zero) has become the preferred form.

I do have one more problem with this code:

 
graphic1.DrawImage(&image1, 10, 10);


For some reason, the image is displaying at a ninth of its size. I checked the GetWidth() and GetHeight() values, and they are correct. The image should be larger than the window itself. Do I need to use any special parameters in DrawImage()? The logical units are in pixels. So, the problem isn't there.
Almost all of the documentation and tutorials I find, seem dated. Even most of MS's documentation seems dated back to Win98.

Yes that's true. But this isn't a problem since not much has changed, GdiPlus is quite new and maybe some other things.

Is it true that the use of "NULL" is also now deprecated?
Yes, but that has nothing to do with the Win APi, it's old C stuff. In C++ you should use nullptr instead.
Thanks Thomas.

I managed to fix the sizing problems by adding width and height parameters to DrawImage()

1
2
3
cxImg = image1.GetWidth();
cyImg = image1.GetHeight();
graphic1.DrawImage(&image1, 0, 0, cxImg, cyImg);


Is this the proper way to handle this? Or, am I using a brute-force approach? What about when I wish to scale the image to fit the window?
Topic archived. No new replies allowed.