Save hidden text into jpeg images

Share your advanced PureBasic knowledge/code with the community.
dige
Addict
Addict
Posts: 1391
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Save hidden text into jpeg images

Post 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
Last edited by dige on Mon May 15, 2006 7:28 am, edited 1 time in total.
syntax error
User
User
Posts: 93
Joined: Tue Jan 13, 2004 5:11 am
Location: Midlands , UK

Post by syntax error »

This is handy. Can the same sort of thing be done on formats such as TARGA, PNG, BMP?
techjunkie
Addict
Addict
Posts: 1126
Joined: Wed Oct 15, 2003 12:40 am
Location: Sweden
Contact:

Post by techjunkie »

Yeah! Really neat... :D Thanks!
Image
(\__/)
(='.'=) This is Bunny. Copy and paste Bunny into your
(")_(") signature to help him gain world domination.
User avatar
Fangbeast
PureBasic Protozoa
PureBasic Protozoa
Posts: 4789
Joined: Fri Apr 25, 2003 3:08 pm
Location: Not Sydney!!! (Bad water, no goats)

Re: Save hidden text into jpeg images

Post 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]
Amateur Radio/VK3HAF, (D-STAR/DMR and more), Arduino, ESP32, Coding, Crochet
dige
Addict
Addict
Posts: 1391
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Post 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.
SFSxOI
Addict
Addict
Posts: 2970
Joined: Sat Dec 31, 2005 5:24 pm
Location: Where ya would never look.....

Post by SFSxOI »

Dare2
Moderator
Moderator
Posts: 3321
Joined: Sat Dec 27, 2003 3:55 am
Location: Great Southern Land

Post by Dare2 »

Nice dige, thanks.


SFSxOI wrote:Steganography ??

http://en.wikipedia.org/wiki/Steganography
Nope. HeaderBlockEnography. :)

Steganography is interesting.
@}--`--,-- A rose by any other name ..
User avatar
Fangbeast
PureBasic Protozoa
PureBasic Protozoa
Posts: 4789
Joined: Fri Apr 25, 2003 3:08 pm
Location: Not Sydney!!! (Bad water, no goats)

Post 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.
Amateur Radio/VK3HAF, (D-STAR/DMR and more), Arduino, ESP32, Coding, Crochet
dige
Addict
Addict
Posts: 1391
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Post 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.
In_Go
New User
New User
Posts: 5
Joined: Fri Apr 07, 2006 9:40 pm
Location: Franconia, Bavaria, Germany

Post 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!
Ad Astra!
Dummy
Enthusiast
Enthusiast
Posts: 162
Joined: Wed Jun 09, 2004 11:10 am
Location: Germany
Contact:

Post 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 ;)
dige
Addict
Addict
Posts: 1391
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Post by dige »

The code above is now ready for pb4 .. and bugfixed ;) thx In_Go!
In_Go
New User
New User
Posts: 5
Joined: Fri Apr 07, 2006 9:40 pm
Location: Franconia, Bavaria, Germany

Post 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
Ad Astra!
In_Go
New User
New User
Posts: 5
Joined: Fri Apr 07, 2006 9:40 pm
Location: Franconia, Bavaria, Germany

Post 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
Ad Astra!
dige
Addict
Addict
Posts: 1391
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Post 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?
Post Reply