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... :D 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.


SFSxOI wrote:Steganography ??

http://en.wikipedia.org/wiki/Steganography
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?