Page 1 of 1
Save hidden text into jpeg images
Posted: Wed Jan 07, 2004 2:00 pm
by dige
Code updated for 5.20+
JPG offers the possibility for remarks etc.. to store ' invisibly ' text
into image header. The text can be read, for instance with
IrfanView.
Code: Select all
; Add comment text, like copyright etc., to jpeg-images
; done by dige 01/2004
; updated to PB4 05/2006
Procedure SwapWord(w.w, Motorola.b)
If Motorola
ProcedureReturn (w & $FF) << 8 + (w >> 8) & $FF
Else
ProcedureReturn w
EndIf
EndProcedure
Procedure.b WriteJpgTxtToFile ( file.s, Comment.s )
Protected Size, success.b, *mem, *pointer
success = #False
Size = FileSize(file)
If Size
FileNr = ReadFile(#PB_Any, file)
If FileNr
*mem = AllocateMemory(Size)
If *mem
*pointer = *mem
ReadData(FileNr, *pointer, Size)
CloseFile(FileNr)
If PeekW(*pointer) & $FFFF = $D8FF
FileNr = CreateFile(#PB_Any, file)
If FileNr
WriteLong (FileNr, $FEFFD8FF ) ; JPG & Comment Marker (Little Endian Format)
WriteWord (FileNr, SwapWord(Len(Comment) + 2, #True)) ; comment lenght incl. size
WriteString(FileNr, Comment )
; Check if there's already a comment text
If PeekW (*pointer + 2 ) & $FFFF = $FEFF
; found comment
Size - SwapWord(PeekW(*pointer + 4), #True) - 4 ; Marker.w + Size.w
*pointer + SwapWord(PeekW(*pointer + 4), #True) + 4
Else
; no comment found
Size - 2 ; $D8FF Marker abziehen
*pointer + 2
EndIf
WriteData(FileNr, *pointer, Size)
CloseFile(FileNr)
success = #True
EndIf
EndIf
EndIf
FreeMemory(*mem)
EndIf
EndIf
ProcedureReturn success
EndProcedure
Debug WriteJpgTxtToFile ( "image.jpg", "(c) by DiGe" )
End
cya dige
Posted: Tue Jan 13, 2004 5:12 am
by syntax error
This is handy. Can the same sort of thing be done on formats such as TARGA, PNG, BMP?
Posted: Tue Jan 13, 2004 7:52 am
by techjunkie
Yeah! Really neat...

Thanks!
Re: Save hidden text into jpeg images
Posted: Tue May 09, 2006 9:24 am
by Fangbeast
dige wrote:JPG offers the possibility for remarks etc.. to store ' invisibly ' text
into image header. The text can be read, for instance with
IrfanView.
Code: Select all
; Add comment text, like copyright etc., to jpeg-images
; done by dige 01/2004
Procedure.b WriteTxtToJpgFile ( File.s, comment.s )
Protected size.l, success.b, *mem.l
success = #False
#FID = 0
size.l = FileSize(File)
If size
*mem = GlobalAlloc_ (#GMEM_FIXED|#GMEM_ZEROINIT, size)
If *mem And ReadFile(#FID, File)
ReadData(*mem, size)
CloseFile(#FID)
If PeekW(*mem) & $FFFF = $D8FF And CreateFile(#FID, File)
WriteLong( $FEFFD8FF ) ; JPG & Comment Marker (Little Endian Format)
WriteByte( $00 ) : WriteByte( Len(comment) + 3 ) ; comment lenght incl. size
WriteString( comment ) : WriteByte( $00 )
If PeekW (*mem + 2 ) & $FFFF = $FEFF
; found comment
size - PeekB(*mem + 5) - 4 : *mem + PeekB(*mem + 5) + 4
Else
; no comment found
size - 2 : *mem + 2
EndIf
WriteData(*mem, size)
CloseFile(#FID)
success = #True
EndIf
EndIf
GlobalFree_( *mem )
EndIf
ProcedureReturn success
EndProcedure
Debug WriteTxtToJpgFile ( "c:\test.jpg", "(c) by DiGe" )
End
cya dige
Dige, being as dumb as politicians, I pb4'ed your code and it works a treat. But I don't know enough to play with it and turn it into a reader as well.
P.s. What is the character limit for info shoved into a jpeg this way?
Code: Select all
[b]; Add comment text, like copyright etc., to jpeg-images
; done by dige 01/2004
Procedure.b WriteTxtToJpgFile (File.s, comment.s)
Protected size.l, success.b, *mem.l
success = #False
#FID = 0
size.l = FileSize(File)
If size
*mem = GlobalAlloc_ (#GMEM_FIXED | #GMEM_ZEROINIT, size)
If *mem And ReadFile(#FID, File)
ReadData(#FID, *mem, size)
CloseFile(#FID)
If PeekW(*mem) & $FFFF = $D8FF And CreateFile(#FID, File)
WriteLong(#FID, $FEFFD8FF ) ; JPG & Comment Marker (Little Endian Format)
WriteByte(#FID, $00)
WriteByte(#FID, Len(comment) + 3 ) ; Comment lenght incl. size
WriteString(#FID, comment )
WriteByte(#FID, $00 )
If PeekW (*mem + 2 ) & $FFFF = $FEFF ; Found comment
size - PeekB(*mem + 5) - 4
*mem + PeekB(*mem + 5) + 4
Else ; No comment found
size - 2
*mem + 2
EndIf
WriteData(#FID, *mem, size)
CloseFile(#FID)
success = #True
EndIf
EndIf
GlobalFree_(*mem)
EndIf
ProcedureReturn success
EndProcedure
Debug WriteTxtToJpgFile ( "k:\test.jpg", "(c) by DiGe" )
End
[/b]
Posted: Tue May 09, 2006 10:19 am
by dige
You can change this part:
Code: Select all
WriteByte(#FID, $00)
WriteByte(#FID, Len(comment) + 3 ) ; Comment lenght incl. size
WriteString(#FID, comment )
to:
Code: Select all
WriteWord(#FID, Len(comment) + 2 ) ; Len Text incl. size
WriteString(#FID, comment )
And that means, you can write 2^16 - 2 Chars as jpeg comment.
Posted: Tue May 09, 2006 12:28 pm
by SFSxOI
Posted: Tue May 09, 2006 12:33 pm
by Dare2
Nice dige, thanks.
Nope. HeaderBlockEnography.
Steganography is interesting.
Posted: Tue May 09, 2006 2:39 pm
by Fangbeast
dige wrote:You can change this part:
Code: Select all
WriteByte(#FID, $00)
WriteByte(#FID, Len(comment) + 3 ) ; Comment lenght incl. size
WriteString(#FID, comment )
to:
Code: Select all
WriteWord(#FID, Len(comment) + 2 ) ; Len Text incl. size
WriteString(#FID, comment )
And that means, you can write 2^16 - 2 Chars as jpeg comment.
Thanks Dige, that's very useful. Now I need to make a reader routine as well. This is for all the family pictures that we have. Very useful to make a database for them.
Posted: Tue May 09, 2006 3:14 pm
by dige
I forgot to tell you, if you use WriteWord () you must change the ByteOrder
like this:
Code: Select all
Procedure SwapWord(w.w)
ProcedureReturn (w & $FF) << 8 + (w >> 8) & $FF
EndProcedure
Code: Select all
WriteWord(#FID, SwapWord(Len(comment) + 2) ) ; Len Text incl. size
WriteString(#FID, comment )
Its very important, otherwise the jpeg is no more readable, coz of wrong
offset.
Posted: Fri May 12, 2006 4:57 pm
by In_Go
Hello Dige!
You made a big !!!! mistake in your Code and it seems nobody except me has noticed it....
If you allocate memory and store that address,
You CANNOT add 2 to that pointer and free that new address later.
Code: Select all
*mem = GlobalAlloc_ (#GMEM_FIXED|#GMEM_ZEROINIT, size)
*mem + 2
GlobalFree_( *mem )
I've seen that mistake for years in your code, and could'nt resist anymore to tell you.
Do invite a new variable for the calculating with same value, but leave *mem unchanged!
Posted: Sun May 14, 2006 8:04 pm
by Dummy
syntax error wrote:This is handy. Can the same sort of thing be done on formats such as TARGA, PNG, BMP?
You can do this with any image container format that has a imageDataPointer in the header.
BMP(yes):
http://en.wikipedia.org/wiki/Windows_bitmap
PNG is even better: you can define your own PNG format as it is a REAL container that divides all data into chunks. So you can add a comment-chunk if it isn't present in current PNG format specification
http://www.libpng.org/pub/png/spec/1.1/ ... tents.html
ok I have to go...you can look for TGA on wikipedia on yourself

Posted: Mon May 15, 2006 7:30 am
by dige
The code above is now ready for pb4 .. and bugfixed ;) thx In_Go!
Posted: Mon May 15, 2006 11:58 pm
by In_Go
Hello Dige!
It seems you did'nt trust my expressions.
Then try out that example!
Code: Select all
;Hello Dige
;
;Try out this codeexample but close all running Tasks
;Then you'll find out what I'm talking about
;It seems that your behavior of changing the
;Memory vector leaves 'holes' in memory list or it
;even destroys the memory list (in Windows)
;After finishing the task
;Windows seems to be
;so clever to cleanup Memory
;So the problem is'nt noticeable if you use it
;just once in a task, what you have done propably.
Declare memory(change.l)
Debug "First with correct freevec"
Debug "This is very fast done without any problems"
;
memory(#False)
;
Debug "Now we do the same with vector change"
Debug "And the problem occurs !"
;
memory(#True)
;
Debug "Done"
Procedure memory(change.l)
; this procedure demonstrates that
; changing *mem Vector generates Errors
size = 100000 ;
sizesp = size
maxsize=size+2000
For n= 1 To 100000
*mem = GlobalAlloc_ (#GMEM_FIXED|#GMEM_ZEROINIT, size)
If *mem
If change.l=#True
*mem+900 ; changing the mem vector
EndIf
GlobalFree_( *mem )
Else
Debug "No Memory available"
EndIf
size +4 :If size>maxsize :size=sizesp :EndIf
Next n
EndProcedure
;With this little modifications Your code works OK ;
Procedure.b WriteTxtToJpgFile (File.s, comment.s)
Protected size.l, success.b, *mem.l
success = #False
#FID = 0
size.l = FileSize(File)
If size
*mem = GlobalAlloc_ (#GMEM_FIXED | #GMEM_ZEROINIT, size)
If *mem And ReadFile(#FID, File)
ReadData(#FID, *mem, size)
CloseFile(#FID)
If PeekW(*mem) & $FFFF = $D8FF And CreateFile(#FID, File)
WriteLong(#FID, $FEFFD8FF ) ; JPG & Comment Marker (Little Endian Format)
WriteByte(#FID, $00)
WriteByte(#FID, Len(comment) + 3 ) ; Comment lenght incl. size
WriteString(#FID, comment )
WriteByte(#FID, $00 )
*mem1 = *mem
If PeekW (*mem + 2 ) & $FFFF = $FEFF ; Found comment
size - PeekB(*mem + 5) - 4
*mem1 + PeekB(*mem + 5) + 4 ;so *mem is not changed
Else ; No comment found
size - 2
*mem1 + 2 ;so *Mem is not changed
EndIf
WriteData(#FID, *mem1, size)
CloseFile(#FID)
success = #True
EndIf
EndIf
GlobalFree_(*mem)
EndIf
ProcedureReturn success
EndProcedure
The Example of your code above should work OK
Posted: Tue May 16, 2006 12:15 am
by In_Go
Hello Dige!
Please Excuse me.
Now I saw your changed code in first topic.
It's a fine peace of code
In_Go
Posted: Tue May 16, 2006 7:14 am
by dige
In_Go wrote:Hello Dige!
It seems you did'nt trust my expressions.
I cant follow you. Have you exactly looked the changed first example?