Follwing on:
How to load large bitmap in FMX (fire monkey)
I have come to a need to draw whats on TBitmapSurface on the FMX.Graphics.TBitmap, i have found a lot of answer regarding this on the web, but they are either in VLC instead of FMX or their goal is saving and loading instead of drawing on a TBitmap, which is why i asked a new question here.
Now here is my current code for loading my image on the TBitmapSurface :
var
bitmapSurf: TBitmapSurface;
path: string;
begin
path := 'image.jpg';
bitmapSurf := TBitmapSurface.Create;
TBitmapCodecManager.LoadFromFile(path, bitmapSurf);
end;
Now after searching for a bit i found that i can use Scanline on the TBitmapSurface, but i didn't know how to use it to draw on the TBitmap, on the web some people had used TBitmap.canvas.draw, but such a thing doesn't exist on the FMX!.
In the end my goal is to draw a very large image (1000*16000) which is loaded in the TBitmapSurface on more then 1 TBitmap (because TBitmap doesn't support more then 8192px and my height is 16000px, i need to draw this on two TBitmap).
I am using Delphi 10.2.3.
Thanks.
You can split the large image (from a file) to two TImage components as follows
Load the image from file to a TBitmapSurface as you already do in your code.
Then create another TBitmapSurface and set its size to the half of the large one. Copy the first half of the large image to this surface and assign it to Image1.Bitmap. Then copy the latter half to this surface and assign that to Image2.Bitmap.
var
srce, dest: TBitmapSurface;
path: string;
scan: integer;
w, h1, h2: integer;
begin
path := 'C:\tmp\Imgs\res.bmp';
srce := TBitmapSurface.Create;
try
TBitmapCodecManager.LoadFromFile(path, srce);
dest := TBitmapSurface.Create;
try
// first half
w := srce.Width;
h1 := srce.Height div 2;
dest.SetSize(w, h1, TPixelFormat.RGBA);
for scan := 0 to h1-1 do
Move(srce.Scanline[scan]^, TBitmapSurface(dest).Scanline[scan]^, srce.Width * 4);
Image1.Bitmap.Assign(dest);
// second half
h2 := srce.Height - h1;
dest.SetSize(w, h2, TPixelFormat.RGBA);
for scan := h1 to srce.Height-1 do
Move(srce.Scanline[scan]^, TBitmapSurface(dest).Scanline[scan-h1]^, srce.Width * 4);
Image2.Bitmap.Assign(dest);
finally
dest.Free;
end;
finally
srce.Free;
end;
Related
I'm trying to draw a simple image with OnPaint method. The code compiles just fine, but when the application starts, it shows "Object lock not owned" error and nothing else happens. Could you please tell me what mistake I made? The code shows the OnPaint event I'm using. Thank you all for your help.
procedure TTabbedForm.Image1Paint(Sender: TObject; Canvas: TCanvas;
const ARect: TRectF);
var
p1, p2, p3, p4, p5, p6: TPointF;
prst1: TRectF;
i :Integer;
begin
Image1.Bitmap.Canvas.Stroke.Color := TAlphaColors.Black;
Image1.Bitmap.Canvas.Stroke.Thickness := 3;
p1 := TPointF.Create(PX, PY);
Image1.Bitmap.Canvas.BeginScene;
with TabbedForm do begin
for i := 0 to 360 do
if (i mod 15)=0 then
begin
p2 := TPointF.Create(Round(PX+PP*sin(i*pi/180)), Round(PY+PP*cos(i*pi/180)));
Image1.Bitmap.Canvas.DrawLine(p1, p2, 100);
end;
for i := 0 to PP do
if (i mod 20)=0 then
begin
prst1 := TRectF.Create(PX+i,PY+i,PX-i,PY-i);
Image1.Bitmap.Canvas.DrawEllipse(prst1, 100);
end;
for i := 0 to 400 do
if (i mod 20)=0 then
begin
p3 := TPointF.Create(i,2*PP);
p4 := TPointF.Create(i,2*PP+2*PP);
Image1.Bitmap.Canvas.DrawLine(p3, p4, 100);
end;
for i := 0 to 400 do
if (i mod 20)=0 then
begin
p5 := TPointF.Create(0,2*PP+i);
p6 := TPointF.Create(2*PP+2*PP,2*PP+i);
Image1.Bitmap.Canvas.DrawLine(p5, p6, 100);
end;
Image1.Bitmap.Canvas.EndScene;
end;
end;
The error message "Object lock not owned" is the message of EMonitorLockException, which is documented to be raised "whenever a thread tries to release the lock on a non-owned monitor". Since you have not responded to my request for an MCVE, and I have not been able to reproduce this error, I can not confirm whether it is due to an unsuccessful lock aquisition through Canvas.BeginScene, or something else.
You can use either a TImage or a TPaintBox for your drawing. Using a TImage provides many benefits such as directly loading an image file, drawing on that image and saving your image to a file directly in various formats, like .bmp, .jpg or .png (maybe others too). A TPaintBox is more lightweight and doesnt have an own bitmap, but uses the parent components surface to draw on (therefore the need for an OnPaint() handler). Loading from / saving to file must be done e.g. through a separate TBitmap.
So yes, you may continue to use a TImage control if you want, but in that case, do not use the OnPaint event for the drawing as you are now. A TImage has a built in mechanism to paint itself when needed. You only need to draw your drawing once to the built-in bitmap canvas. In the following code the image is drawn in a ButtonClick() event. Also note, that with the TImage you must use BeginScene - EndScene correctly as documented.
You must also set the TImage.Bitmap.Size before drawing on it. If this was not set elsewhere in your code of what you have shown, then that may be another reason why your code produced no image.
Draw your image on Image1.Bitmap.Canvas e.g. in a OnClick() event of a button:
procedure TTabbedForm.Button1Click(Sender: TObject);
var
p1, p2, p3, p4, p5, p6: TPointF;
prst1: TRectF;
i: integer;
begin
Image1.Bitmap.SetSize(300, 300); // must be set before call to BeginScene
if Image1.Bitmap.Canvas.BeginScene then
try
Image1.Bitmap.Canvas.Stroke.Color := TAlphaColors.Black;
Image1.Bitmap.Canvas.Stroke.Thickness := 1;
p1 := TPointF.Create(px, py);
for i := 0 to 360 do
if (i mod 15) = 0 then
begin
pp := i;
p2 := TPointF.Create(Round(px + pp * sin(i * pi / 180)),
Round(py + pp * cos(i * pi / 180)));
Image1.Bitmap.Canvas.DrawLine(p1, p2, 100);
end;
for i := 0 to pp do
...
for i := 0 to 400 do
...
for i := 0 to 400 do
....
finally
Image1.Bitmap.Canvas.EndScene;
end;
end;
I think you get this error message, because you're drawing on the canvas at a time when you're not allowed to. Potential causes for this are:
You're drawing on the bitmap of the image from the paint event of the image. Images are for displaying pre-generated or loaded bitmaps, and since modifying the bitmap should trigger the OnPaint event, I think it's a bad idea to make those changes from that same event. It's asking for an endless loop, or other unwanted side effects.
You're using BeginScene/EndScene incorrectly. You should only proceed drawing if BeginScene returns true. And actually it's not needed to call them at all when drawing on the given canvas of a paint event.
You're (partially) using a global instance of the form instead of the current instance (Self), which could (depending on your application), lead to drawing on the wrong instance.
Small disclaimer: I left your code as-is as much as possible, just changed the things that I think could potentially cause your problem. I think these changes all make sense, but I must admit I've never done much painting in FMX, so maybe some of these are a bit naive or over-protective (or blatantly wrong).
Things that are different in this code compared to yours:
Use a TPaintbox (you'll have to add a TPaintbox named 'Paintbox1', and add this method to it's OnPaint handler). Paintboxes are for direct drawing. You could also keep the image, if you would be able to pre-render the image's bitmap on specific events, like the start of your application, a click of a button, a timer, and so on.
Correct use of BeginScene and EndScene, with an if and a try..finally block. BeginScene will give you a lock or not, and return a boolean depending on the success. You should only proceed if you actually acquired the lock, and only call EndScene in that case too, because they are ref counted, and doing this wrong could screw up the refcount, and therefor all further painting in your application.
Stroke settings inside the scene as well. Not 100% sure if needed, but I guess it's part of drawing the scene too, right?
Left out BeginScene..EndScene completely. The Paintbox or Image control should already have called that itself. See FMX.Graphics.TCanvas.BeginScene docs
Just use Canvas. It's passed as a parameter to the event handler, so better to use that, then to try and find the right canvas yourself.
Removed the with. This is a bit of a long shot, but it looked like you were referring to a global TTabbedForm variable, and since you are inside a TTabbedForm method, you should be able to use the properties and methods of the current instance as-is, or prepend with Self. if you run into naming conflicts. It's always better to not rely on those globals for forms and datamodules, and you'll actually run into problems if you want to have multiple instances of your form, in which case your original code would partially operate on the wrong instance.
procedure TTabbedForm.Paintbox1Paint(
Sender: TObject; Canvas: TCanvas; const ARect: TRectF);
var
p1, p2, p3, p4, p5, p6: TPointF;
prst1: TRectF;
i :Integer;
begin
p1 := TPointF.Create(PX, PY);
Canvas.Stroke.Color := TAlphaColors.Black;
Canvas.Stroke.Thickness := 3;
for i := 0 to 360 do
if (i mod 15)=0 then
begin
p2 := TPointF.Create(Round(PX+PP*sin(i*pi/180)), Round(PY+PP*cos(i*pi/180)));
Canvas.DrawLine(p1, p2, 100);
end;
for i := 0 to PP do
if (i mod 20)=0 then
begin
prst1 := TRectF.Create(PX+i,PY+i,PX-i,PY-i);
Canvas.DrawEllipse(prst1, 100);
end;
for i := 0 to 400 do
if (i mod 20)=0 then
begin
p3 := TPointF.Create(i,2*PP);
p4 := TPointF.Create(i,2*PP+2*PP);
Canvas.DrawLine(p3, p4, 100);
end;
for i := 0 to 400 do
if (i mod 20)=0 then
begin
p5 := TPointF.Create(0,2*PP+i);
p6 := TPointF.Create(2*PP+2*PP,2*PP+i);
Canvas.DrawLine(p5, p6, 100);
end;
end;
I'm creating some .wmf files, but some of them seem corrupted and can't be shown in any metafile viewer. After some trial and error, I found that the problem is caused by their dimensions. If I scale the same drawing by a factor to reduce the dimensions, it will be shown.
Now, I want to know if there's a limitation on the size of drawing or if the problem is something else. I know that these files have a 16-bit data structure, so I guess that the limitation would be 2^16 units in each dimension, (or 2^15 if it's signed). But in my tests it is around 25,000. So I can't rely on this value since the limitation can be on anything (Width*Height maybe, or maybe the resolution of the drawing may affect it). I can't find a reliable resource about .wmf files that describes this.
Here is sample code that shows the problem:
procedure DrawWMF(const Rect: TRect; const Scale: Double; FileName: string);
var
Metafile: TMetafile;
Canvas: TMetafileCanvas;
W, H: Integer;
begin
W := Round(Rect.Width * Scale);
H := Round(Rect.Height * Scale);
Metafile := TMetafile.Create;
Metafile.SetSize(W, H);
Canvas := TMetafileCanvas.Create(Metafile, 0);
Canvas.LineTo(W, H);
Canvas.Free;
Metafile.SaveToFile(FileName);
Metafile.Free;
end;
procedure TForm1.Button1Click(Sender: TObject);
const
Dim = 40000;
begin
DrawWMF(Rect(0, 0, Dim, Dim), 1.0, 'Original.wmf');
DrawWMF(Rect(0, 0, Dim, Dim), 0.5, 'Scaled.wmf');
try
Image1.Picture.LoadFromFile('Original.wmf');
except
Image1.Picture.Assign(nil);
end;
try
Image2.Picture.LoadFromFile('Scaled.wmf');
except
Image2.Picture.Assign(nil);
end;
end;
PS: I know that setting Metafile.Enhanced to True and saving it as an .emf file will solve the problem, but the destination application that I'm generating files for doesn't support Enhanced Metafiles.
Edit:
As mentioned in answers below, there are two different problems here:
The main problem is about the file itself, it has a 2^15 limitation on each dimension. If either width or height of the drawing overpasses this value, delphi will write a corrupted file. You can find more details in Sertac's answer.
The second problem is about loading the file in a TImage. There's another limitation when you want to show the image in a delphi VCL application. This one is system dependent and is related to dpi of DC that the drawing is going to be painted on. Tom's answer describes this in details. Passing 0.7 as Scale to DrawWMF (code sample above) reproduces this situation on my PC. The generated file is OK and can be viewed with other Metafile viewers (I use MS Office Picture Manager) but VCL fails to show it, however, no exception is raised while loading the file.
Your limit is 32767.
Tracing VCL code, the output file gets corrupt in TMetafile.WriteWMFStream. VCL writes a WmfPlaceableFileHeader (TMetafileHeader in VCL) record and then calls GetWinMetaFileBits to have 'emf' records converted to 'wmf' records. This function fails if any of the dimensions of the bounding rectangle (used when calling CreateEnhMetaFile) is greater than 32767. Not checking the return value, VCL does not raise any exception and closes the file with only 22 bytes - having only the "placeable header".
Even for dimensions less than 32767, the "placeable header" may have possible wrong values (read details about the reason and implications from Tom's answer and comments to the answer), but more on this later...
I used the below code to find the limit. Note that GetWinMetaFileBits does not get called with an enhanced metafile in VCL code.
function IsDimOverLimit(W, H: Integer): Boolean;
var
Metafile: TMetafile;
RefDC: HDC;
begin
Metafile := TMetafile.Create;
Metafile.SetSize(W, H);
RefDC := GetDC(0);
TMetafileCanvas.Create(Metafile, RefDC).Free;
Result := GetWinMetaFileBits(MetaFile.Handle, 0, nil, MM_ANISOTROPIC, RefDC) > 0;
ReleaseDC(0, RefDC);
Metafile.Free;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
i: Integer;
begin
for i := 20000 to 40000 do
if not IsDimOverLimit(100, i) then begin
ShowMessage(SysErrorMessage(GetLastError)); // ReleaseDc and freeing meta file does not set any last error
Break;
end;
end;
The error is a 534 ("Arithmetic result exceeded 32 bits"). Obviously there's some signed integer overflow. Some 'mf3216.dll' ("32-bit to 16-bit Metafile Conversion DLL") sets the error during a call by GetWinMetaFileBits to its exported ConvertEmfToWmf function, but that doesn't lead to any documentation regarding the overflow. The only official documentation regarding wmf limitations I can find is this (its main point is "use wmf only in 16 bit executables" :)).
As mentioned earlier, the bogus "placeable header" structure may have "bogus" values and this may prevent the VCL from correctly playing the metafile. Specifically, dimensions of the metafile, as the VCL know them, may overflow. You may perform a simple sanity check after you have loaded the images for them to be displayed properly:
var
Header: TEnhMetaHeader;
begin
DrawWMF(Rect(0, 0, Dim, Dim), 1.0, 'Original.wmf');
DrawWMF(Rect(0, 0, Dim, Dim), 0.5, 'Scaled.wmf');
try
Image1.Picture.LoadFromFile('Original.wmf');
if (TMetafile(Image1.Picture.Graphic).Width < 0) or
(TMetafile(Image1.Picture.Graphic).Height < 0) then begin
GetEnhMetaFileHeader(TMetafile(Image1.Picture.Graphic).Handle,
SizeOf(Header), #Header);
TMetafile(Image1.Picture.Graphic).Width := MulDiv(Header.rclFrame.Right,
Header.szlDevice.cx, Header.szlMillimeters.cx * 100);
TMetafile(Image1.Picture.Graphic).Height := MulDiv(Header.rclFrame.Bottom,
Header.szlDevice.cy, Header.szlMillimeters.cy * 100);
end;
...
When docs don't help, look at the source :). The file creation fails if the either width or height is too big, and the file becomes invalid. In the following I look at the horizontal dimension only, but the vertical dimension is treated the same.
In Vcl.Graphics:
constructor TMetafileCanvas.CreateWithComment(AMetafile : TMetafile;
ReferenceDevice: HDC; const CreatedBy, Description: String);
FMetafile.MMWidth := MulDiv(FMetafile.Width,
GetDeviceCaps(RefDC, HORZSIZE) * 100, GetDeviceCaps(RefDC, HORZRES));
If ReferenceDevice is not defined, then the screen (GetDC(0)) is used. On my machine horizontal size is reported as 677 and horizontal resolution as 1920. Thus FMetafile.MMWidth := 40000 * 67700 div 1920 ( = 1410416). Since FMetaFile.MMWidth is an integer, no problems at this point.
Next, let's look at the file writing, which is done with WriteWMFStream because we write to a .wmf file:
procedure TMetafile.WriteWMFStream(Stream: TStream);
var
WMF: TMetafileHeader;
...
begin
...
Inch := 96 { WMF defaults to 96 units per inch }
...
Right := MulDiv(FWidth, WMF.Inch, HundredthMMPerInch);
...
The WMF header structure indicates where things are going south
TMetafileHeader = record
Key: Longint;
Handle: SmallInt;
Box: TSmallRect; // smallint members
Inch: Word;
Reserved: Longint;
CheckSum: Word;
end;
The Box: TSmallRect field can not hold bigger coordinates than smallint-sized values.
Right is calculated as Right := 1410417 * 96 div 2540 ( = 53307 as smallint= -12229). The dimensions of the image overflows and the wmf data can not be 'played' to the file.
The question rizes: What dimensions can I use on my machine?
Both FMetaFile.MMWidth and FMetaFile.MMHeight needs to be less or equal to
MaxSmallInt * HundredthMMPerInch div UnitsPerInch or
32767 * 2540 div 96 = 866960
On my testmachine horizontal display size and resolution are 677 and 1920. Vertical display size and resolution are 381 and 1080. Thus maximum dimensions of a metafile becomes:
Horizontal: 866960 * 1920 div 67700 = 24587
Vertical: 866960 * 1080 div 38100 = 24575
Verified by testing.
Update after further investigation inspired by comments:
With horizontal and vertical dimension up to 32767, the metafile is readable with some applications, f.ex. GIMP, it shows the image. Possibly this is due to those programs considering the extents of the drawing as word instead of SmallInt. GIMP reported pixels per inch to be 90 and when changed to 96 (which is the value used by Delphi, GIMP chrashed with a 'GIMP Message: Plug-in crashed: "file-wmf.exe".
The procedure in the OP does not show an error message with dimensions of 32767 or less. However, if either dimension is higher than previously presented calculated max value, the drawing is not shown. When reading the metafile, the same TMetafileHeader structure type is used as when saving and the FWidth and FHeight get negative values:
procedure TMetafile.ReadWMFStream(Stream: TStream; Length: Longint);
...
FWidth := MulDiv(WMF.Box.Right - WMF.Box.Left, HundredthMMPerInch, WMF.Inch);
FHeight := MulDiv(WMF.Box.Bottom - WMF.Box.Top, HundredthMMPerInch, WMF.Inch);
procedure TImage.PictureChanged(Sender: TObject);
if AutoSize and (Picture.Width > 0) and (Picture.Height > 0) then
SetBounds(Left, Top, Picture.Width, Picture.Height);
The negative values ripple through to the Paint procedure in the DestRect function and the image is therefore not seen.
procedure TImage.Paint;
...
with inherited Canvas do
StretchDraw(DestRect, Picture.Graphic);
DestRect has negative values for Right and Bottom
I maintain that the only way to find actual limit is to call GetDeviceCaps() for both horizontal and vertical size and resolution, and perform the calculations above. Note however, the file may still not be displayable with a Delphi program on another machine. Keeping the drawing size within 20000 x 20000 is probably a safe limit.
In Delphi I have an unknown number of image file names stored in a details table. These image files can be Bitmaps, Jpegs, PNGS and ICO files.
What's the paradigm / best practice to load and display those in a listview or a listbox on the go?
I take it I would somehow need to load those to a ImageList in OnBeforeScroll event of a master table and then assign that to a listview. Database components used are dbGO.
I only need to display thumbnails of predefined size (in a VCL program).
The simplest method is to use TPicture, since the loading of different graphic formats is already implemented and you do have to care about different image classes .
You have to ensure that the required units are included in the with uses so here e.g. jpeg, gifimg, and pngimg.
After loading with TPicture.LoadFromFile the images are painted, centered and scaled, on a prepared Bitmap with the dimensions of the Imagelist.
Last step is simply to call teh AddBitmap procedure with the Bitmap and nil for the mask.
// make sure you included the needed units
// uses pngImage,jpeg,gifimg;
Procedure LoadImagesFromDataset2ImageList(il: TImageList; DS: TDataset; const FileFieldname: String);
var
P: TPicture;
bmp: TBitmap;
Function CalcRectAndPrepare: TRect; // calculate Rect for here centered/streched output
var // and fill the bitmap with the desired beckground color
f: Double;
begin
bmp.Canvas.Brush.Color := clWhite;
bmp.Canvas.FillRect(Rect(0, 0, bmp.Width, bmp.Height));
if P.Width > P.Height then
f := bmp.Width / P.Width
else
f := bmp.Height / P.Height;
Result.Left := Round(bmp.Width - P.Width * f) div 2;
Result.Top := Round(bmp.Height - P.Height * f) div 2;
Result.Right := bmp.Width - Result.Left;
Result.Bottom := bmp.Height - Result.Top;
end;
begin
P := TPicture.Create;
bmp := TBitmap.Create;
try
bmp.Width := il.Width;
bmp.Height := il.Height;
DS.First;
while not DS.Eof do
begin
if FileExists(DS.Fieldbyname(FileFieldname).asString) then
begin
P.LoadFromFile(DS.Fieldbyname(FileFieldname).asString);
bmp.Canvas.StretchDraw(CalcRectAndPrepare, P.Graphic);
il.Add(bmp, nil);
end;
DS.Next;
end;
finally
P.Free;
bmp.Free;
end;
end;
"Unknown number" sounds like there may be a huge number of images. So pre-rendered thumbnails would be very helpful. If your application can create thumbnails for all images and keep them in a separate database this would reduce the CPU resource usage for shrinking them. And you could reference the thumbnail database from your master database.
One thing I would check if RAM could be a limitation is how many instances of the actual thumbnail will be created in your application, for example if you load 1000 database records which all refer to the same thumbnail, does the database access component allocate 1000 image objects (using 1000 times more RAM than needed) or only one, which is referenced 1000 times. Also the de-allocation of the image data is important.
I have discovered that animated GIFs created using Delphi 2009's TGIFImage sometimes doesn't play correctly in some GIF viewers. The problem is that the animation is restarted prematurely.
Consider the following example:
program GIFAnomaly;
{$APPTYPE CONSOLE}
uses
Windows, Types, Classes, SysUtils, Graphics, GIFImg;
var
g: TGIFImage;
bm: TBitmap;
procedure MakeFrame(n: integer);
var
x: Integer;
y: Integer;
begin
for x := 0 to 256 - 1 do
for y := 0 to 256 - 1 do
bm.Canvas.Pixels[x, y] := RGB((x + n) mod 255,
(x + y - 2*n) mod 255, (x*y*n div 500) mod 255);
end;
var
i: integer;
begin
bm := TBitmap.Create;
bm.SetSize(256, 256);
g := TGIFImage.Create;
g.Animate := true;
for i := 0 to 499 do
begin
MakeFrame(i);
TGIFGraphicControlExtension.Create(g.Add(bm)).Delay := 3;
Writeln('Creating frame ', i+1, ' of 500.');
end;
TGIFAppExtNSLoop.Create(g.Images.Frames[0]).Loops := 0;
g.SaveToFile('C:\Users\Andreas Rejbrand\Desktop\test.gif');
end.
(This is the simplest example I could find that exhibits the problem.)
The output is a rather large animated GIF. In Internet Explorer 11, the entire 15-second 'movie' is played properly, but in Google Chrome the 'movie' is prematurely restarted after only about four seconds.
Why is this?
Is there something wrong with the output GIF file?
If so, is there something wrong with my code above, or is there a problem with GIFImg?
If not, what is the nature of the problem in the viewer? What fraction of the available viewers have this problem? Is there a way to 'avoid' this problem during GIF creation?
For the benefit of the SO user, the above code is a minimal working example. Of course, I wasn't creating these psychedelic patterns when I discovered the issue. Instead, I was working on a Lorenz system simulator, and produced this GIF animation which does play in IE but not in Chrome:
In Internet Explorer 11, the model is rotated 360 degrees before the animation is restarted. In Google Chrome, the animation is restarted prematurely after only some 20 degrees.
The Lorenz image works in Internet Explorer 11.0.9600.17239, The GIMP 2.8.0, Opera 12.16
The Lorenz image does not work in Google Chrome 36.0.1985.143 m, Firefox 26.0, 27.0.1, 31.0.
If I open a 'problematic' GIF in The GIMP and let GIMP (re)save it as an animated GIF, the result works in every viewer. The following is the GIMPed version of the Lorenz animation:
Comparing the two files using a hex editor, and using the Wikipedia article as a reference, it seems, for instance, like the 'NETSCAPE' string is at the wrong place in the original (unGIMPed) version. It is somewhat strange, that even if I set the width and height of the GIF image, the corresponding values in the Logical Screen Descriptor are not there.
It's a bug in TGIFImage's LZW encoder.
In some very rare circumstances the LZW encoder will output an extra zero byte at the end of the LZW steam. Since the LZW end block marker is also a zero byte, a strict GIF reader might choke on this or interpret it as the end of the GIF (although the end of file marker is $3B).
The reason some GIF readers can handle this is probably that GIFs with this problem was common many years ago. Apparently TGIFImage wasn't the only library to make that particular mistake.
To fix the problem make the following modification to gifimg.pas (change marked with *):
procedure TGIFWriter.FlushBuffer;
begin
if (FNeedsFlush) then
begin
FBuffer[0] := Byte(FBufferCount-1); // Block size excluding the count
Stream.WriteBuffer(FBuffer, FBufferCount);
FBufferCount := 1; // Reserve first byte of buffer for length
FNeedsFlush := False; // *** Add this ***
end;
end;
Edit: This turned out not to be the answer but I'm keeping it as the rule about the loop extension still applies.
The NETSCAPE loop extension must be the first extension:
var
Frame: TGIFFrame;
...
for i := 0 to 499 do
begin
MakeFrame(i);
Frame := g.Add(bm);
if (i = 0) then
TGIFAppExtNSLoop.Create(Frame).Loops := 0;
TGIFGraphicControlExtension.Create(Frame).Delay := 3;
Writeln('Creating frame ', i+1, ' of 500.');
end;
See: The TGIFImage FAQ.
Apart from that I see nothing wrong with your GIF, but you could reduce the size a bit with a global color table.
I am trying to convert a bitmap file to rtf using Delphi 2007.
I used below code for conversion.
function BitmapToRTF(pict: TBitmap): string;
var
bi, bb, rtf: string;
bis, bbs: Cardinal;
achar: ShortString;
hexpict: string;
I: Integer;
begin
GetDIBSizes(pict.Handle, bis, bbs);
SetLength(bi, bis);
SetLength(bb, bbs);
GetDIB(pict.Handle, pict.Palette, PChar(bi)^, PChar(bb)^);
rtf := '{\rtf1 {\pict\dibitmap0 ';
SetLength(hexpict, (Length(bb) + Length(bi)) * 2);
I := 2;
for bis := 1 to Length(bi) do
begin
achar := Format('%x', [Integer(bi[bis])]);
if Length(achar) = 1 then
achar := '0' + achar;
hexpict[I - 1] := achar[1];
hexpict[I] := achar[2];
Inc(I, 2);
end;
for bbs := 1 to Length(bb) do
begin
achar := Format('%x', [Integer(bb[bbs])]);
if Length(achar) = 1 then
achar := '0' + achar;
hexpict[I - 1] := achar[1];
hexpict[I] := achar[2];
Inc(I, 2);
end;
rtf := rtf + hexpict + ' }}';
Result := rtf;
end;
Now my problem is i was not able to view the image in MS Word or Viewer.
But i can view the image in word pad.
Please suggest me in solving this problem.
I think the problem is that the Word implementation for RTF rendering asks for more information than the Wordpad's one (I think that for security reasons -avoid overflow attacks-), but this is pure speculation I must confess.
Try being accurate when describing your bitmap info: for example if the bitmap is 32-bit use \wbmbitspixel32, put the width and height in your rtf encoding with \picw and \pich, etc. May be you have luck with that.
Here is an example of this:
http://www.scribd.com/doc/25967552/Rtf1-Ansi-Ansicpg1252-Uc2-Deff0-Deflang1033-Fonttbl-f0-Froman-Fcharset0-Fprq2-Panose-02020603050405020304-Times-New-Roman-f1-Fswiss-Fchar
How can you convert an image to a textfile?
RTF is RichtTextFormat i guess?
I would take the bmp and put it with the Microsoft Word API into a Document and save the document was rtf.
Tobi
If you want to view the image in MS Word or Word Viewer, convert the image to EMF file and embed it inside the RTF tags. (Note: here you cant view the image in Wordpad)
{\rtf1 {\pict\emfblif <emf source> }}
If you want to view the image in Wordpad, convert the image into bitmap and embed it inside the RTF tags.
{\rtf1 {\pict\dibitmap0 <bitmap source> }}
I dont know why this happens.