PDA

View Full Version : Saving graphics to non-standard files



Crisp_N_Dry
25-01-2003, 11:16 PM
I am attempting to save a bitmap image to a file that only my program can understand (this is more than just changing the file extension). I have tried using blockread/blockwrite but when I tried to load the image back to another bitmap, the bitmap is just blank. The code goes something like below.

procedure SavePic(FileName: String);
var
OldBitmap: TBitmap;
Data: File;
begin
OldBitmap:=TBitmap.Create;
OldBitmap.Picture.LoadFromFile('C:\MyPicture.Bmp') ;
AssignFile(Data,'MyPicture.Dat');
Rewrite(Data,Sizeof(Bitmap);
BlockWrite(Data,Bitmap,1);
OldBitmap.Free;
end;

procedure LoadPic(FileName: String);
var
NewBitmap: TBitmap;
Data: File;
begin
NewBitmap:=TBitmap.Create;
NewBitmap.Picture.LoadFromFile('C:\MyPicture.Bmp') ;
AssignFile(Data,'MyPicture.Dat');
Reset(Data,Sizeof(Bitmap);
BlockRead(Data,Bitmap,1);
Canvas.Draw(0,0,NewBitmap);
NewBitmap.Free;
end;

But it won't work, any suggestions or alternative ways I can save the pic without anyone being able to edit the pictures.

Alimonster
25-01-2003, 11:39 PM
I didn't look at your example very long but perhaps you'd be better saving the pic using a for loop for the y axis, BlockWriting each scanline in turn (because the padding might screw up things, not sure).

Check out this page on EFG's site: http://www.efg2.com/Lab/ImageProcessing/CryptImage.htm. If you used that then you could apply it once and use SaveToFile/LoadFromFile without any more fuss. XOR encryption isn't the strongest in the world but it'll do for starters. You can always add different, better encryption to the whole file using a block cipher later on.

Crisp_N_Dry
26-01-2003, 12:23 AM
I did spend a small amount of time trying to save a series of scanlines but the problem is that a scanline stores a pointer and not the actual data so if I tried to save a series of scanlines then I would be saving pointers which obviously wouldn't work, well, as I say I only spent a small amount of time trying that so maybe I was doing it poorly/wrong.

Alimonster
26-01-2003, 11:58 AM
Each scanline is a pointer to an array of pixels for that row, so you can save it by saving the first element of the scanline array. Here's some generalised code for saving 16, 24 and 32 bit bmps as RAW files (the usual disclaimers apply):


[background=#FFFFFF][comment=#0000FF][normal=#000000]
[number=#C00000][reserved=#000000][string=#00C000]type
TRawHeader = record
Width: Integer;
Height: Integer;
BitsPerPixel: Integer;
end;
PRawHeader = ^TRawHeader;

TPixelArray = array[0..0] of Byte;
PPixelArray = ^TPixelArray;

const
BitCount: array[TPixelFormat] of Integer = (-1, 1, 4, 8, 15, 16, 24, 32, -1);

// the following doesn't handle pfCustom
function PixelFormatToInt(PixFormat: TPixelFormat): Integer;
var
DC: HDC;
begin
if PixFormat = pfDevice then // get current screen bpp
begin
DC := GetDC(0);
try
// todo: confirm this is correct
Result := GetDeviceCaps(DC, PLANES) * GetDeviceCaps(DC, BITSPIXEL);
finally
ReleaseDC(0, DC);
end;
end
else
if (PixFormat >= pf1Bit) and (PixFormat <= pf32Bit) then
Result := BitCount[PixFormat]
else
raise Exception.Create('Unexpected pixel format in PixelFormatToInt');
end;

function IntToPixelFormat(BitsPerPixel: Integer): TPixelFormat;
begin
case BitsPerPixel of
1 : Result := pf1Bit;
4 : Result := pf4Bit;
8 : Result := pf8Bit;
15: Result := pf15Bit;
16: Result := pf16Bit;
24: Result := pf24Bit;
32: Result := pf32Bit;
else
raise Exception.CreateFmt('Unexpected bits per pixel count in IntToPixelFormat: %d', [BitsPerPixel]);
end;
end;

//
// SaveAsRaw
//
// loads up the bitmap file given by InFile (e.g. "c:\windows\setup.bmp") and
// saves it to a raw file in the given pixel format
//
procedure SaveAsRaw(const InFilename, OutFilename: String; PixFormat: TPixelFormat);
var
bmp: TBitmap;
i: integer;
F: File;
ThisLine: PPixelArray;
Header: TRawHeader;
BitsPerPixel: Integer;
WidthOfScanline: Integer;
begin
BitsPerPixel := PixelFormatToInt(PixFormat);

if not (BitsPerPixel in [16, 24, 32]) then
raise Exception.Create('SaveAsRaw only supports 16, 24 and 32 bpp at present');

if not FileExists(InFilename) then
raise Exception.Create('The file ' + InFilename + ' does not exist');

bmp := TBitmap.Create;
try
bmp.LoadFromFile(InFilename);
bmp.PixelFormat := PixFormat;

Header.Width := Bmp.Width;
Header.Height := Bmp.Height;
Header.BitsPerPixel := BitsPerPixel;

AssignFile(F, OutFilename);
try
Rewrite(F, 1);

WidthOfScanline := Bmp.Width * (BitsPerPixel div 8); // bytes per scanline

BlockWrite(F, Header, SizeOf(TRawHeader));

for i := 0 to Bmp.Height - 1 do
begin
ThisLine := Bmp.ScanLine[i];
BlockWrite(F, ThisLine[0], WidthOfScanline);
end;
finally
CloseFile(F);
end;
finally
Bmp.free;
end;
end;

//
// LoadBitmapFromRAW
//
// Loads the RAW file given by filename into the supplied Bmp. The result
// will be a DIB of the RAW's pixel format
//
procedure LoadBitmapFromRAW(const Filename: String; Bmp: TBitmap);
var
F: File;
Header: TRawHeader;
y: Integer;
WidthOfScanline: Integer;
begin
if not FileExists(Filename) then
raise Exception.Create('The file ' + Filename + ' does not exist!');

AssignFile(F, Filename);
try
Reset(F, 1);

BlockRead(F, Header, SizeOf(TRawHeader));

if not (Header.BitsPerPixel in [16, 24, 32]) then
raise Exception.Create('LoadBitmapFromRAW only supports 16, 24 or 32 bits per pixel at present');

Bmp.PixelFormat := IntToPixelFormat(Header.BitsPerPixel);
Bmp.Width := Header.Width;
Bmp.Height := Header.Height;

WidthOfScanline := Bmp.Width * (Header.BitsPerPixel div 8); // bytes per scanline

for y := 0 to Header.Height - 1 do
begin
BlockRead(F, PPixelArray(Bmp.Scanline[y])^[0], WidthOfScanline);
end;
finally
CloseFile(F);
end;
end;

////////////////////////////////////////////////////////////////////////////////
// QUICK TEST PROCEDURES
////////////////////////////////////////////////////////////////////////////////

procedure TForm1.btnSaveClick(Sender: TObject);
begin
SaveAsRaw('c:\windows\setup.bmp', 'c:\test.raw', pf24Bit);
end;

procedure TForm1.btnLoadClick(Sender: TObject);
var
Bmp: TBitmap;
begin
Bmp := TBitmap.Create;
try
LoadBitmapFromRAW('c:\test.raw', Bmp);
Canvas.Draw(0,0, Bmp);
finally
Bmp.Free;
end;
end;

You'd definitely want to encrypt them though! See the EFG link; saving in binary format with BlockWrite gives you no additional protection. You can also have a look around Torry (http://www.torry.net) for encryption components - there are plenty free ones there.

Crisp_N_Dry
26-01-2003, 05:11 PM
Cheers for that AliMonster, muchos gracias. Also, the encryption part is no biggy 'cause I just wanted to stop the lamen from loading them into Paint and messing them up.

Thanks Again

BojZ
15-03-2003, 04:45 PM
Ello!

It works perfectly if you want to store one pic (with data) to one file, how about several pics with data to the same file? (An array...)

Tried to make it work with some modifications to the code above but without success so far... :(

Alimonster
17-03-2003, 09:29 AM
This topic was mentioned in another thread somewhere (forgot which one), and I ended up writing a small article (http://www.alistairkeys.co.uk/packing.shtml) on it. That may be of some help to you.

It's possible to skip the header or footer part in the packing tutorial entirely, though I wouldn't recommend it. You can pass in an array of TBitmap to the writing function and loop over every TBitmap to save each as a raw file. When reading in the files, you'd pass an index for the wanted raw. Reading it would be a case of reading each header, figuring out how many bytes the raw takes, and seeking to the next one for however many times are required. Each loop will take you to the next header if done correctly, which means that you will eventually reach the wanted raw file - but this will be a little annoying.

I'd recommend writing out a header or footer though, since that will both make things easier and allow you more customisation (CRCs, per-file options, and so on).