c++c++builderc++builder-12-athens

How to load an Image file of BMP, PNG, JPG


I am trying to load and resize an image. I have an OpenPictureDialog to select the file. The dialog filters are set to *.bmp|*.jpg|*.jpeg|*.png. I run the app and get an error when opening a PNG.

Project HDLite.exe raised exception class EInvalidGraphic with message 'Bitmap image is not valid'.

My thinking is: I'm using the <Graphics::TBitmap>, but I'm unable to figure out which to use. iNet research indicated that <Graphics::TBitmap> was the correct class.

void __fastcall TMainForm::LogoImageClick(TObject* Sender)
{
    int AIndex = ControlList1->ItemIndex;

    if (ImageModule->OpenPictureDialog1->Execute()) {
        std::unique_ptr<Graphics::TBitmap> srcBitmap(new Graphics::TBitmap());
        std::unique_ptr<Graphics::TBitmap> dstBitmap(new Graphics::TBitmap());
        String AFileName =
            ExtractFileName(ImageModule->OpenPictureDialog1->FileName);
        FSettings->LogoFileName[AIndex] =
            TPath::Combine(ExtractFilePath(FSettings->FileName), AFileName);

        try {
            srcBitmap->LoadFromFile(ImageModule->OpenPictureDialog1->FileName);  //<-- ERROR
            dstBitmap->SetSize(64, 64);
            srcBitmap->Canvas->StretchDraw(Rect(0, 0, 64, 64), dstBitmap.get());
            dstBitmap->SaveToFile(FSettings->LogoFileName[AIndex]);

        } catch (...) {
            MessageBeep(0);
        }
    }
}

As a bonus: I would also like the SaveToFile() to always save the resized image as a PNG.


Solution

  • I run the app and get an error when opening a PNG.

    That is because the VCL's TBitmap class only supports BMP images.

    In VCL, different image formats are handled by different TGraphic-derived classes, such as:

    Or, you can use Vcl::Graphics::TWICImage, which can handle any image format that Microsoft's Windows Imaging Component supports.

    You need to use the appropriate class for each file type you want to load. Or, you can use Vcl::Graphics::TPicture instead and let it figure that out for you. Its LoadFromFile() method will look at the file extension and create an instance of the appropriate TGraphic class, which you can then access from the TPicture::Graphic property.

    Otherwise, switch to FireMonkey instead of VCL. The Fmx::Graphics::TBitmap class behaves the way you want - it supports multiple input formats (which vary depending on platform), and can save to PNG.

    As a bonus: I would also like the SaveToFile() to always save the resized image as a PNG.

    To do that in VCL, you will have to first Assign() the modified image to a separate TPngImage object (if you are not already resizing a TPngImage object) and then save that.

    With all of that said, try something like this:

    #include <Vcl.Graphics.hpp>
    #include <Vcl.Imaging.PngImage.hpp>
    #include <Vcl.Imaging.Jpeg.hpp>
    #include <Vcl.Imaging.GIFImg.hpp>
    
    void __fastcall TMainForm::LogoImageClick(TObject* Sender)
    {
        int AIndex = ControlList1->ItemIndex;
    
        if (ImageModule->OpenPictureDialog1->Execute()) {
            try {
                auto srcPicture = std::make_unique<TPicture>();
                srcPicture->LoadFromFile(ImageModule->OpenPictureDialog1->FileName);
    
                auto srcBitmap = std::make_unique<TBitmap>();
                srcBitmap->Assign(srcPicture->Graphic);
    
                auto dstBitmap = std::make_unique<TBitmap>();
                dstBitmap->SetSize(64, 64);
                dstBitmap->Canvas->StretchDraw(Rect(0, 0, 64, 64), srcBitmap.get());
    
                auto dstPng = std::make_unique<TPngImage>();
                dstPng->Assign(dstBitmap.get());
    
                String AFileName = TPath::Combine(
                    ExtractFilePath(FSettings->FileName),
                    ChangeFileExt(ExtractFileName(ImageModule->OpenPictureDialog1->FileName), _D(".png"))
                );
                dstPng->SaveToFile(AFileName);
                FSettings->LogoFileName[AIndex] = AFileName;
    
            } catch (...) {
                MessageBeep(0);
            }
        }
    }