How to use CreateMemoryStream and PictureStream

Knowledge exchange related to the VPE Report Engine and PDF Library

Moderator: IDEAL Software Support

How to use CreateMemoryStream and PictureStream

Postby Brent Rose » Thu Oct 29, 2015 2:25 am

Hi

I have been unable to get PictureStream to work properly. (VCL Ver 7.00 with XE5)

By way of an example in the code below, I am attempting to open a JPG file, copy it to a TVPEStream and then insert it into a doc using PictureStream. (In practise, I'm wanting to use a database BlobStream as the source rather than a file.)

RESULT: I get LastError = VERR_PIC_IMPORT after attempting to output the image with PictureStream.

As indicated in the code, if I use "Picture" to print the image directly from the source file, I have no problem (so I assume there is nothing wrong with the JPG image as such).

A check of AFileStream and AVPEStream indicates that both have the same size (32404 in this case) so there is no apparent data loss.

Thanks.

Code: Select all
procedure TForm1.Button1Click(Sender: TObject);
var
  AFileStream: TFileStream;
  Buffer: TBytes;
  BytesToRead: Integer;
  BufSize, ACount: Integer;
  AVPEStream: TVPEStream;
begin
  with VPEngine1 do
  begin
    OpenDoc;

    AVPEStream := CreateMemoryStream(16000);

    //stream from file
    AFileStream := TFileStream.Create('E:\Temp\MyImage.jpg', fmOpenRead);
    try
      //copy to VPEStream
      BytesToRead := AFileStream.Size;
      AFileStream.Position := 0;
      BufSize := 16000;
      SetLength(Buffer, BufSize);
      try
        while (BytesToRead > 0) do
        begin
          if (BytesToRead > BufSize) then
            ACount := BufSize
          else
            ACount := BytesToRead;
          AFileStream.ReadBuffer(Buffer, ACount);
          AVPEStream.Write(Buffer, ACount);
          Dec(BytesToRead, ACount);
        end;
      finally
        SetLength(Buffer, 0);
      end;
    finally
      AFileStream.Free;
    end;
    AVPEStream.Seek(0);

    //print picture stream
    PictureBestFit := True;
    PictureKeepAspect := True;

    //this fails with LastError = VERR_PIC_IMPORT (300)
    PictureStream(AVPEStream, 2, 2, 12, VFREE, 'test');
    ShowMessageFmt('LastError = %d', [LastError]);

    //using the image file directly succeeds
    //Picture(2, 2, 12, VFREE, 'E:\Temp\MyImage.jpg');

    AVPEStream.Free;

    Preview;
  end;
end;
Brent Rose
 
Posts: 50
Joined: Wed Mar 21, 2012 8:13 pm

Re: How to use CreateMemoryStream and PictureStream

Postby IDEAL Software Support » Thu Oct 29, 2015 5:07 pm

We tested this in C++ and it works as it should. Can you step with the debugger into stream.Write() and verify, if the buffer's contents are identical to those provided in the method call? Just to be sure that the "const buffer" declaration works as it should. You should also verify the "size" parameter.
As a next check, call stream.GetSize() and check the result.
As a final step, instead of writing to a VPE memory stream, write the buffer (TBuffer) to a new file, and see if it is still a valid jpeg.

One extra hint: You should always explicitly call stream.Close() before freeing the stream object. This frees the memory occupied by VPE's memory stream immediately. Otherwise all streams keep using their memory until the document is closed.
IDEAL Software Support
 
Posts: 1621
Joined: Thu Nov 18, 2004 4:03 pm

Re: How to use CreateMemoryStream and PictureStream

Postby Brent Rose » Thu Oct 29, 2015 10:36 pm

More information:

1. Using the 16000 "chunk" with the code above and the 32404 byte image file, the stream sizes change as expected.

ie The source file stream reports a size of 32404; BytesToRead steps from 32404 to 16404 to 404 to 0 as expected.
The VPEStream size steps from 0 to 16000 to 32000 to 32404 as expected. The final VPEStream size matches the source stream size.

2. I note that the maximum chunk size = 19336 before the TVPEStream.Write function causes an AV.

ie CreateMemoryStream will accept a chunk size of 19337 or more, but a Buffer of this size will cause an AV when you try to write it to TVPEStream.

3. If I stream the same Buffer to another TFileStream, the resulting file has the same size as the original, is readable as a JPG image in Windows, and will also output correctly in VPE using the "Picture" method.

4. If I run the report as coded above a SECOND time (without closing the test app), I always get Delphi error "Invalid pointer operation" when calling AVPEStream.Free.

NB I have added a call to AVPEStream.Close before freeing the stream as you suggest, but this does not change this error as such.

By way of a simple test, I tried the following code which does nothing but create and free a VPEStream... the same error occurs when the (blank) report is repeated a second time.

Code: Select all
procedure TForm1.Button11Click(Sender: TObject);
var
  AVPEStream: TVPEStream;
begin
  with VPEngine1 do
  begin
    OpenDoc;

    AVPEStream := CreateMemoryStream(16000);
    AVPEStream.Close;
    AVPEStream.Free;

    Preview;
  end;
end;


5. Using smaller 100 byte chunks for ease of comparison, the buffer passed to VPEStream.Write and the buffer within this method call, when viewed in the debugger, are identical.
Brent Rose
 
Posts: 50
Joined: Wed Mar 21, 2012 8:13 pm

Re: How to use CreateMemoryStream and PictureStream

Postby IDEAL Software Support » Fri Oct 30, 2015 8:55 am

Our Delphi knowledge is a bit rusty. In Delphi never call Free for VPE objects, except they have been created using CreateCopy.

We now created a Delphi project and we could reproduce that the JPEG file can not be imported using your code.

Then we changed the declaration of "Buffer: TBytes" to

Buffer: array [0..20000] of byte;

and it works.

We also can not reproduce any AV using this declaration. We can use any buffer size for the VPE memory stream.

As we are no Delphi gurus (our home is C++), maybe you can shed some light onto, why "Buffer: TBytes;" does not work?
IDEAL Software Support
 
Posts: 1621
Joined: Thu Nov 18, 2004 4:03 pm

Re: How to use CreateMemoryStream and PictureStream

Postby Brent Rose » Fri Oct 30, 2015 11:18 pm

Thanks! That has it all sorted.

1. You are quite right re "never call Free for VPE objects". Doing so is quite automatic and conventional for Delphi objects, of course, and with my call to "Free" I assumed I was doing what actually needed to be done with a call to "Close". I even left the Free call there after following your hint about "Close", missing the greater point. Clearly, I need to remember that VPE-VCL is interfacing with a DLL, and dealing with objects that are managed within the DLL, not Delphi objects as such, :wink:

2. In hindsight, the Buffer type issue should have been more obvious to me for the same reason (ie because I'm dealing with a DLL). I should have recognised the need to speak "plain and simple" to the DLL - not with a generic open array type like TBytes.

TBytes = TArray<Byte>;

TArray<T> = array of T;

As an open array, a variable of TBytes needs a call to SetLength to set its size... All this all makes it a somewhat different beast to a garden variety "fixed array of Byte". Beyond this, I'm not clever enough to explain exactly why it doesn't work :wink: Maybe TBytes can be suitably cast, or maybe that amounts to just copying it to a fixed array anyway.

Thanks again.
Brent Rose
 
Posts: 50
Joined: Wed Mar 21, 2012 8:13 pm


Return to VPE Open Forum

Who is online

Users browsing this forum: Google [Bot] and 11 guests