0

I am currently trying to read an Image from a MS Access database that has an OLE Object field and contains a valid bitmap (for test purposes, I created a image using MS Paint and saved it in 24bit bmp).

I am linking to this via DBGrid. In theory everything should work good and it should show the image, however I am getting a: "bitmap image not valid" error. I can understand if this is a JPEG and not .bmp, but that isn't the case. So my question is, what is wrong?

I dont necessarily have to use a DBImage, a normal TImage will also do just fine (might even be more preferable), but I'm not sure on how to assign a TImage to an OLE Object field in a MS Access Database. I Have tried, to no avail:

//Select photo from Image field  
Image1.Picture := ADOTable1['Image'];

I've read most of the articles, such as about.com etc, regarding this matter, but still don't get any good results.

Any help would be greatly appreciated!

UPDATE: This worked for me:

Add to USES clause : JPEG, ADODB, DB

function JpegStartsInBlob
(PicField:TBlobField):integer;
var
 bS     : TADOBlobStream;
 buffer : Word;
 hx     : string;
begin
 Result := -1;
 bS := TADOBlobStream.Create(PicField, bmRead);
 try
 while (Result = -1) and
    (bS.Position + 1 < bS.Size) do
 begin
  bS.ReadBuffer(buffer, 1);
  hx:=IntToHex(buffer, 2);
  if hx = 'FF' then begin
  bS.ReadBuffer(buffer, 1);
  hx:=IntToHex(buffer, 2);
  if hx = 'D8' then Result := bS.Position - 2
  else if hx = 'FF' then
    bS.Position := bS.Position-1;
end; 
end; 
finally
bS.Free
end;  
end;

procedure TfrmOne.btnShowImageClick(Sender: TObject);
var
 bS : TADOBlobStream;
 Pic : TJPEGImage;
begin

bS := TADOBlobStream.Create(table1.FieldByName('Photo') as TBlobField, bmRead);   
bS.Seek(JpegStartsInBlob(table1.FieldByName('Photo') as TBlobField),
        soFromBeginning);
Pic := TJPEGImage.Create;
Pic.LoadFromStream(bS);
frmOne.Image1.Picture.Graphic := Pic;
Pic.Free;
bS.Free;
end;
coder123
  • 67
  • 4
  • 10

1 Answers1

0

what would return ADOTable1['Image'] ?

i don't like FieldVallues property u use, for you don't know the actual type and cannot control things using type checking. I guess you'd better use Data.DB.TDataSet.FieldByName The very type of TField object would hint you the kind of data it contains.

I don't know about Microsoft Jet (database engine of Excel and Access), but i think it stores raw BMP file data and some link to Paint application (or Gimp, or Photoshop, or whatever used to edit BMP) Kinf of TBlobField i think.

http://support.microsoft.com/kb/205635/en-us - this shows a snippet how to save file from OLE filed.

try to find a way to save field content into TFileStream.

Check that created file is really BMP file. After that save blob into TMemoryStream and TBitmap.LoadFromStream

consider Data.DB.TBlobField.SaveToStream, Data.DB.TField.AsBytes

One more snippet from docs - explore those classes if u can use them "Use TADOBlobStream to access or modify the value of a BLOB or memo field in an ADO dataset. BLOB fields are represented by TBlobField objects and descendants of TBlobField such as TGraphicField and TMemoField."

As resume:

1) get the type of data - ADOTable1.FieldByName.ClassName Read about it, which methods properties would allwo u to get the data 2) try to save the data into file and analyze what is it 3) try to save that data into stream and re-use for loading picture

Arioch 'The
  • 15,799
  • 35
  • 62
  • Well to be honest, I don't really have a clue what you are saying. When I use the code as said in my original question, I get an access violation. Is there something specific I need to do, since it really sounds like that is only what I have to do to assign the image to the database field. I can't think that is is that difficult? – coder123 Jul 18 '12 at 16:11
  • If you want to be programmer - you have to have a clue. There is noe "image" it is what might some redneck from the field say. There is a certain data structure. And Delphi has tisown,, Acces has its own, OLe has its own, etc. You should pass and transform the data structures throught them. TButton is a button, TBitBtn is a button, TSpeedButton is a button... but those are different buttons. TBitmap, TImage, TGraphic - those all are pictures. But different pictures. So you should have train yourself to KNOW what-the-data-structure is behind each name. If you do not know it - you just throw – Arioch 'The Jul 19 '12 at 10:09
  • random dice. Random access to memory is exactly access violation. You've got it. you are lucky you know it, it could be much worse: no visible error, but your program just destorys the database silently. To be programmer you should be curios like a cat and pedantic like an old man. At same time. You have to learn to find information and read it. Like told in SmartQuestionsFAQ. So start doing it step by step. I would not say you shhould learn assembler (though it helps, to catch compiler errors and not do slow stupid codes) But you should understand what is each data structure in each run step – Arioch 'The Jul 19 '12 at 10:15
  • So take good book like Delphi Foundations. Take Delphi Help (it was rather good in Delphi 5, it is somewhat helpful in XE2, it was awful in D2006. No one knows of your version. But take it and about each term train urself to read what this data structure offers and what it expects from you. – Arioch 'The Jul 19 '12 at 10:17
  • Then take above 1-2-3 steps and try to do them. One after another. It is like LEGO. Find matching cubes and connect them the way they can be connected. And make he whole construct small and rigid enough to not fall apart. Take step 1 and do it. – Arioch 'The Jul 19 '12 at 10:18
  • Image1.Picture := 'Image'; Image1.Picture := 2+3; Image1.Picture := ADOTable1; Would you be surprised that can not work ? Image1.Picture := ADOTable1['Image'] can not work too; Only some TPicture object can be assigned to .Picture. You have to get thet TPicture object. And don't use those := when it is about complex properties liek objects. Only for numbers and strings. Or you would cheat yourself and would make "memory leak". Frankly it should be like var p: TPicture; p := (* some creation of TPicture *); p.LoadFrom... (some data loading); – Arioch 'The Jul 19 '12 at 10:24
  • Image1.Picture.Assign(p); (* copy data from p into TImage *); p.Free; (*kill the object when u know more need it and free the memory of its burden *). Read documentation about objects and their life time and memory management. I again suggest u getting book like Delphi Foundations and understanding spirit behind ESR's SmartQuestions FAQ. Or quitting for Java, C#, Python, etc - they would hide that lifetime and memory details from you and you would not know of your error until all the computer runs out of memory and slows like a snail :-D – Arioch 'The Jul 19 '12 at 10:28
  • Thanks! I was baffled at the beginning at what you were trying to say, but after some googling and trying/investigating myself, I got success. Usually I am always very keen to learn what is going on, but this time I didn't have a clue where to begin. Thanks again! I used the code in my original answer. Just one thing, as you can see, it's very inefficient to use it on each show picture button. How can I call the procedure from another form, but this time with other a database table (table2) and another field ('Graphic')? I presume the procedure form must be include in uses clause. Thanks! – coder123 Jul 19 '12 at 14:09
  • i don;t understand u - what u call inefficiency, what do you want to get rid of, what new features do u want to obtain ? And why u call it by button ? Maybe you'd better call it when current record in ADOTable1 changed up or down? – Arioch 'The Jul 19 '12 at 19:38
  • did u saw http://stackoverflow.com/questions/6251504/ ? Arnaud is great smart guy, usually does good software – Arioch 'The Jul 19 '12 at 19:45
  • "bS.ReadBuffer(buffer, 1);" Well, WORD type is 2 bytes, one byte would probably cotain garbage. Try setting buffer := $ffff; as 1st line of proc - would it work then? your are lucky! // Also, i think now you SHOULD learn of assembler. Compare numbers via int-to-hex is such a weird idea... You basically create new reference-counted object just for.... What you really wanted i guess was *var buf: byte; ...ReadBuffer... if buf = $ff then ...* – Arioch 'The Jul 19 '12 at 19:51
  • You creating ADOBLOBStream in two placed over one field. Why ? It is not efficient. You do search via list of field two times on each call. And you search by string. It is inefficient. 1) change function JpegStartsInBlob so it get TStream inside, not TField. In btnReserveInfoClick call JpegStartsInBlob(bS). You already created it - why create again ? – Arioch 'The Jul 19 '12 at 19:59
  • good practice is safe memory freeing. Always. Even if error happened. *var:=TClass.Create; try ..... finally var.Free; end* - better do that for next to every object. Imaging: Blob was empty, fucntion returned -1. Seek failed. Baaaaam!!! none of .Free was called. Memory leaked. /// some people when many objects save on exceptions stack by single frame: *v2 := nil; v3 := nil; ...v9 := nil; v1 := T.Create; try v2:= T.Create; ... v9 := T.Create; .... finally v1.Free; v2.Free; ... v8.Free;* That is a bit less safe but a bit faster and less EXE size. I don't like it though. But many do it. – Arioch 'The Jul 19 '12 at 20:01
  • don't call table1.FieldByName('Photo') so many times i think. Did you disigned fields in IDE ? if yes - then you form probably have object for it. If no - then you can catch dataase after-open event, introduce variable in form and cahce field there to search only once. Sorry, i don't know ADO and so cannot suggest details, only principle. Principle is "do long calculations/search once, cache result until you can discard it". At least think about this and apply time to time. – Arioch 'The Jul 19 '12 at 20:05
  • coming back to *if buf = $ff then* and *change function JpegStartsInBlob so it get TStream inside, not TField* - make now TWO fucntions. One would always check via IntToHex (btw, what if one day it would return 'ff' not 'FF' ? :-) and another via byte $ff. Make one TADOBlobStream and not re-reading it to keep data same, just do a loop. Call 100000 times one function searching in the blob by string and int-to-hex, and 100000 times new function searching by byte. Compare times. And tell them please. – Arioch 'The Jul 19 '12 at 20:13
  • You really make poor computer do a lot of absolutely not-needed work. Use integers where u could - it is much better than strings. Much faster and much less chance of error. Another variant is to make function where one IF goes with byte and another with IntTOHex and such. Go *Procject / options / compiler* check *use debug DCUs* go *Procect/Build* put breakpoints before if (where byte) and before IntToHex (as it really is part of comparision, you should coutn it together with if). On each breakpoint open *View / Debug Windows / CPU Window* and trace the if calculation in assemler codes. – Arioch 'The Jul 19 '12 at 20:16
  • from breakpoint start and to moment where it goes to next Pascal line out of if. I bet that byte version would be just two commands: CMP and JE or JNE. I bet that you would be rather tired to trace all the int-hex related stuff until u come out of IF. And after u do it, you would think a bit how tired was CPU to do it. Good luck. Keap learning. – Arioch 'The Jul 19 '12 at 20:17
  • and ShellAPI - i cannot see where it neede in this snippet. maybe for some other code - but why here ? i see no need for it. will this code searchign for jpeg stop compiling if u comment-out ShellAPI ? – Arioch 'The Jul 19 '12 at 20:21
  • Wow. How how am going to answer all those questions. For starters, I got my code from http://delphi.about.com/od/database/l/aa030601d.htm. Secondly. What I am trying to is: Use the procedure I made and call it in another procedure. For example. I have a button. If I press it, it should display the image from the database. That is what my code is for. However, I don't think its good coding practice to type both the function and procedure on each button that displays the image. This is where I want to know, can I link to one procedure and use that, instead of using all that code in each button? – coder123 Jul 19 '12 at 20:22
  • Oh and please reduce the comments. I take your advice on learning assembler (have worked once or twice with it, although very basically), but thats the code that I used and what worked for me. So I just want to know if I can 'call' it on another forum, without having to type it out again in each display image button. "You really make poor computer do a lot of absolutely not-needed work." I'm not really sure where this comes in? – coder123 Jul 19 '12 at 20:30
  • "If you want to be programmer - you have to have a clue." I referred to not having a clue, since I'm not always sure where you are. Meaning, one moment your busy in one comment with something, the next you are somewhere else and thats what confuses me. – coder123 Jul 19 '12 at 20:46
  • ShellAPI was added when I copied the code and I asumed it needed to be declared, my fault. Thanks for pointing out I don't need it. Since this doesn't really have anything more to do with DBImage, I am going to rather open up a new topic. Thanks for your help! – coder123 Jul 19 '12 at 20:48
  • i took you code and tryied to tweak a bit. I don't have Delphi here so i just edited it on http://pastebin.ca/2172739 // i hope it would compile. I hope that http://docwiki.embarcadero.com/Libraries/en/Vcl.Graphics.TPicture.Graphic applies to Delphi 7 with little changes. Maybe it would be better, maybe not. But - before you would use it - do those things i asked u before yourself, just as experience. And that time-measuring loop, please do it and look and numbers. And i am curios to know them as well. :-) – Arioch 'The Jul 19 '12 at 20:52
  • Excellent thanks! Will take a look, appreciate all your trouble. – coder123 Jul 19 '12 at 20:55
  • about delphi.about.com, what can i say.... I am impressed. But even in Delphi sources u can mean stupid code. Let as say, that guy shoued basic sceleton and tried to make the code easy to understanding, not to running, wanted u to look and make u code, not to copy-paste as is. I hope so. Otherwise... Brrr... /// don't trust anyone. don't trust me and don't trust yourself. i tols u word could lead to errors - then implememnt that word := $ffff and test - was i right or worng. was that guy wrong or right. (about trusting urself i meant DUnit and TDD) – Arioch 'The Jul 19 '12 at 21:13
  • "You really make poor computer do a lot of absolutely not-needed work" - make that time measuring or CPU Window tracing about byte comparison and string comparison with IntToHex conversion. You would c. Same goal but one road is straight and one is super-curvy. Some code just aking to being re-worked. If u had assembler and have a clew how AnsiString is managed and ref-counted, then i wonder how u could copy-paste that without crying in terror and instantly remaking it. Also after assembler courses thing like WORD is one byte should alert u instantly as well. Not after thinking. Just on sight. – Arioch 'The Jul 19 '12 at 21:17
  • Yes. Usually I look and try to do my own code, but since this was the first time I tried something like this, I wasn't sure what was going on, so I copied to see if it actually works in my case and it did, after that, I went back, read the article and learned what the code did. If I didn't try that, it would have taken a long time for me to figure it out myself. That's why he made the guide, so one can learn. So that area I'm not unsure on, it was just if I can use the code on another form, without using it again. By this I mean 'reference' it. – coder123 Jul 19 '12 at 21:20