1

Here I have a GUID for BMP:

const      
  GBmp: TGUID =  '{b96b3cab-0728-11d3-9d7b-0000f81ef32e}';

I have some initialization with GdiplusStartup(...). Then I load a JPG and the ShowMessage says 'Ok'

  Err :=GdipLoadImageFromFile('C:\a.jpg', GdiImage);
  ShowMessage(ShowError(TGPStatus(err)));

Now I try to save into BMP but I get 'FileNotFound':

  Err :=GdipSaveImageToFile(GdiImage, 'C:\b.bmp', @GBmp, nil);
  ShowMessage(ShowError(TGPStatus(err)));

How can I save to PNG or BMP with GDI+? I don't want to use third party libraries, just the DLL.

Below is the full code for Delphi7+:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TGDIStartup = packed record
    Version: Integer; // =1
    DebugEventCallback: Pointer; //For debug
    SuppressBackgroundThread: Bool;
    SuppressExternalCodecs: Bool; //Tru to use internal codecs
  end;

type
  TEncoderParameter = record
    Guid : TGUID;
    NumberOfValues : ULONG;
    Type_ : ULONG;
    Value : Pointer;
  end;
  TEncoderParameters = record
    Count : UINT;
    Parameter : array[0..0] of TEncoderParameter;
  end;
  PEncoderParameters = ^TEncoderParameters;

type
  TGPStatus = (
    Ok,
    GenericError,
    InvalidParameter,
    OutOfMemory,
    ObjectBusy,
    InsufficientBuffer,
    NotImplemented,
    Win32Error,
    WrongState,
    Aborted,
    FileNotFound,
    ValueOverflow,
    AccessDenied,
    UnknownImageFormat,
    FontFamilyNotFound,
    FontStyleNotFound,
    NotTrueTypeFont,
    UnsupportedGdiplusVersion,
    GdiplusNotInitialized,
    PropertyNotFound,
    PropertyNotSupported,
    ProfileNotFound
  );

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  function GdipSaveImageToFile(image: Integer; filename: PWCHAR; clsidEncoder: PGUID; encoderParams: PEncoderParameters): Integer; stdcall;
  function GdipLoadImageFromFile(const Filename: PWideChar; out Image: Integer): Integer; stdcall;
  function GdiplusStartup(var Token: Longword; const Input, Output: Pointer): Integer; stdcall;
  function GdipGetImageHeight(Image: Integer; out Height: Integer): Integer; stdcall;

var
  Form1: TForm1;

implementation

{$R *.dfm}

function GdipSaveImageToFile;    external 'GdiPlus.dll'  name 'GdipSaveImageToFile';
function GdipLoadImageFromFile;  external 'GdiPlus.dll'  name 'GdipLoadImageFromFile';
function GdiplusStartup;         external 'GdiPlus.dll'  name 'GdiplusStartup';
function GdipGetImageHeight;     external 'GdiPlus.dll'  name 'GdipGetImageHeight';

function ShowError(Code: TGPStatus) : String;
begin
  case Code of
    Ok                        : Result := 'Ok';
    GenericError              : Result := 'GenericError';
    InvalidParameter          : Result := 'InvalidParameter';
    OutOfMemory               : Result := 'OutOfMemory';
    ObjectBusy                : Result := 'ObjectBusy';
    InsufficientBuffer        : Result := 'InsufficientBuffer';
    NotImplemented            : Result := 'NotImplemented';
    Win32Error                : Result := 'Win32Error';
    WrongState                : Result := 'WrongState';
    Aborted                   : Result := 'Aborted';
    FileNotFound              : Result := 'FileNotFound';
    ValueOverflow             : Result := 'ValueOverflow';
    AccessDenied              : Result := 'AccessDenied';
    UnknownImageFormat        : Result := 'UnknownImageFormat';
    FontFamilyNotFound        : Result := 'FontFamilyNotFound';
    FontStyleNotFound         : Result := 'FontStyleNotFound';
    NotTrueTypeFont           : Result := 'NotTrueTypeFont';
    UnsupportedGdiplusVersion : Result := 'UnsupportedGdiplusVersion';
    GdiplusNotInitialized     : Result := 'GdiplusNotInitialized';
    PropertyNotFound          : Result := 'PropertyNotFound';
    PropertyNotSupported      : Result := 'PropertyNotSupported';
    ProfileNotFound           : Result := 'ProfileNotFound';
  else
    Result := '<UnKnown>';
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
const GuidBmp: TGUID = '{b96b3cab-0728-11d3-9d7b-0000f81ef32e}';
      GuidPng: TGUID = '{b96b3caf-0728-11d3-9d7b-0000f81ef32e}';
var Err: Integer;
    Graphics: Integer;

    InitToken: LongWord;
    Startup: TGDIStartup;
    GdiImage: Integer;
    Hei: Integer;
begin
  FillChar(Startup, sizeof(Startup), 0);
  Startup.Version := 1;
  GdiplusStartup(InitToken, @Startup, nil);

  Err := GdipLoadImageFromFile('C:\_MyDat\gdi\a.jpg', GdiImage);
  ShowMessage(ShowError(TGPStatus(Err)));

  GdipGetImageHeight(GdiImage, Hei);
  ShowMessage(IntToStr(Hei));

  Err := GdipSaveImageToFile(GdiImage, 'C:\_MyDat\gdi\b.bmp', @GuidPng, nil);
  ShowMessage(ShowError(TGPStatus(Err)));
end;

end.
Tom
  • 2,962
  • 3
  • 39
  • 69
  • you will have to provide the third parameter the `Encoderparameters` http://msdn.microsoft.com/en-us/library/windows/desktop/ms535407(v=vs.85).aspx – bummi May 03 '13 at 22:52
  • @bummi It says it's optional. I tried with jpeg, bmp and png and in every case it is required? No way... – Tom May 03 '13 at 22:59
  • 1
    I'd say that your code should work. It's hard to help because you didn't supply a complete program. If you'd supplied a complete program, I could paste it into my dev env and run it just like that. But in order to try things out, I need to spend time writing a program that you already wrote. And I haven't got the desire to do that. – David Heffernan May 03 '13 at 23:11
  • 1
    @Tom have you tried saving some other type of file (e.g. "C:\b.txt") to the same location? Sounds like it could be a permissions problem; in any case, if you've tried some debugging please say what's worked and what has not. – Argalatyr May 04 '13 at 02:22
  • @DavidHeffernan I'm sorry, I thought I am doing something obviously stupid. I have attached full, simplified code of the problem. – Tom May 04 '13 at 10:47
  • @Argalatyr I save to a folder with my program, so it does have permissions to write. I also tried different locations and different TGUID-s – Tom May 04 '13 at 10:48
  • How do you know that your code is able to write a file there? Have you written a file from your program? Testing each step is the only way to know what's broken. – Argalatyr May 04 '13 at 10:54
  • @Argalatyr I used this: `List := TStringList.Create; List.Add('Test'); List.SaveToFile('C:\_MyDat\gdi\b.txt');` and it saved a file in the same dir with no problems. – Tom May 04 '13 at 10:57
  • 1
    @Argalatyr I also added this line: `ShowMessage(IntToStr(Hei));` to the program, as you can see in the full listing and it shows correct height of the JPEG image I load. So the image IS loading just fine and I can WRITE in that directory. There is only 1 line of code: `GdipSaveImageToFile(...)` which doesn't work I and don't know to debug this line. – Tom May 04 '13 at 10:59
  • 1
    Seems you found your bug - nice work. – Argalatyr May 06 '13 at 16:00
  • A small hint for other readers: Please don't forget `GdipDisposeImage`, otherwise, the input file stays locked. – Daniel Marschall Aug 02 '17 at 08:36

1 Answers1

3

Seems I was using wrong GUID-s. These are correct:

gGIf: TGUID = '{557CF402-1A04-11D3-9A73-0000F81EF32E}';
gPNG: TGUID = '{557CF406-1A04-11D3-9A73-0000F81EF32E}';
gPJG: TGUID = '{557CF401-1A04-11D3-9A73-0000F81EF32E}';
gBMP: TGUID = '{557CF400-1A04-11D3-9A73-0000F81EF32E}';
gTIF: TGUID = '{557CF405-1A04-11D3-9A73-0000F81EF32E}';

Seems the previous ones are only for reading:

Tom
  • 2,962
  • 3
  • 39
  • 69
  • 1
    I think you are meant to get the CLSID by passing a MIME type to the API: http://msdn.microsoft.com/en-us/library/ms533843(v=vs.85).aspx – David Heffernan May 04 '13 at 12:02
  • @DavidHeffernan I saw some solutions based on that approach but I don't really see a point in using 1 more function. The GUID-s are not supposed to change anyway. I think they made this GetEncoderClsid() function just to make it easier for programmers to write code with GDI+ (remembering GUID-s is hard) but using GDI without a wrapper is a hell anyway. – Tom May 04 '13 at 15:17
  • 1
    If the GUIDs were meant to be permanent, Windows would have provided a manifest constant for the various types. No, I think they are meant to change, and you should always enumerate them if you don't want your program to break under some future release of Windows. – Pierre Sep 16 '13 at 00:27