Hide image in image

Share your advanced PureBasic knowledge/code with the community.
User avatar
BasicallyPure
Enthusiast
Enthusiast
Posts: 536
Joined: Thu Mar 24, 2011 12:40 am
Location: Iowa, USA

Hide image in image

Post by BasicallyPure »

Edit: 9/30/2017 the code below has been updated to version 2.1
New features:
1. The insertion image will automatically shrink to fit the container image if needed.
2. The black border around the extracted image can be trimmed off automatically.
3. All of the hidden images are now encrypted. You can choose a password or use the
program's default password. The default password is more convenient but if anyone else
uses a copy of this program they will be able to reveal the hidden image.

Image

I have devised a method to hide an image within another image.

How it works:
First you use the 'Load container' button to choose a container image.
The least significant bits (LSBs) of each pixel color of the container image are used to store the hidden information.

Next use the 'Load insertion' button to choose an image to hide in the container image,
it does not have to be the same size (x,y dimensions).
If the insertion (hidden) image is smaller than the container then it will be positioned in the center of the container.
IF it is larger than the container it will be resized to fit the container.

Use the 'Merge images' button to hide the insertion image in the container image.
The insertion image is processed in the following way.
The color palette is reduced to a simple basic 8-color palette.
Along with the color reduction a Sierra Lite dithering operation is performed.
When those two steps have been accomplished then the secret image is inserted into the container image.

Use the 'Save composite' button to save your new image with the secret hidden image.
If you have the password option enabled go ahead and enter any password.
If the password is longer than 32 characters, only the last 32 are relevant.
Make sure to use a lossless image format such as .bmp or .png.

Use the 'Load composite' button to retrieve a previously saved image with a secret hidden image.
To extract the hidden image use the 'Extract' button.
If the extracted image appears as a jumble of pixels there are one of two possibilities.
1) The composite image you loaded does not actually have a hidden image within it.
2) The composite image was saved with the password option, you must enable the password option
and enter the correct password after you click the 'Extract' button.

My code makes use of a couple of good posts made by Wilbert.
I use his 'Nearest Color Module', and his 'gaussian blur' procedures.
They are both included in the code below.
I have modified his nearest color module to use a default basic 8-color palette required for my method.
His module also produces the Sierra Lite dithering effect.

When the hidden image is extracted the gaussian blur is applied to produce a result that more closely resembles the original image.

Clipboard copy and paste are also supported to aid in selecting and manipulating your images.

If you want to save an extracted image use the 'Copy' button to place it in the clipboard.
Open another image editing program such as 'Paint' then paste the image in and save.

Note to Linux users: don't use the latest PB version 5.61 because of the ImageGadget bug.

Code: Select all

; Hide_Image_in_Image.pb
; by BasicallyPure
; 09.29.2017
; version 2.1
; License: free and risky
; Compiler: PB 5.60
; should run on all OS.

EnableExplicit

UseJPEGImageDecoder() : UsePNGImageDecoder() : UsePNGImageEncoder()

CompilerIf #PB_Compiler_Processor = #PB_Processor_x86
   Macro rdx : edx : EndMacro
   #x64 = #False
CompilerElse
   #x64 = #True
CompilerEndIf

DeclareModule NearestColor
   ; NearestColor module by Wilbert
   ; http://www.purebasic.fr/english/viewtopic.php?f=12&t=61475
   ; Latest updated : Jan 27, 2016
   ; Color distance formula based on:
   ; http://www.compuphase.com/cmetric.htm
   ; Dithering method: Sierra Lite
   ; Note:
   ; some unused procedures were reomved by BasicallyPure
   ; and simplified for a default 8-color basic pallette.
   
   Declare.i DitheredImage(Image.i, DitherLevel.a = 220)
   Declare.l FindNearest(Color.l)
   
EndDeclareModule

Module NearestColor
   
   EnableASM
   EnableExplicit
   DisableDebugger
   
   Global Dim IndexG.l(255)
   Global Dim Palette.l(9)
   
   CompilerIf #PB_Compiler_Processor = #PB_Processor_x86
      Macro rdx : edx : EndMacro
   CompilerEndIf
   
   Palette(0) = 0 : Palette(9) = 0 ; basic 8-color pallette
   Palette(1) = $FF000000 : Palette(2) = $FF0000FF : Palette(3) = $FF00FF00 : Palette(4) = $FFFF0000
   Palette(5) = $FF00FFFF : Palette(6) = $FFFF00FF : Palette(7) = $FFFFFF00 : Palette(8) = $FFFFFFFF
   SortStructuredArray(Palette(), 0, 0, #PB_Unicode, 1, 8)
   
   Define.i i, j=1
   
   For i = 0 To 255
      IndexG(i) = j
      While ((Palette(j) >> 8) & $FF) = i And j < 8
         j + 1
      Wend
      IndexG(i) = (IndexG(i) + j) >> 1
   Next
   
   Macro M_FindNearest(i, st)
      !nearestcolor.findnearest#i#_loop:
      !mov ecx, [p.v_c#i#]
      !test ecx, ecx
      !jz nearestcolor.findnearest#i#_cont2
      !movzx eax, byte [p.v_Color + 1]
      !movzx ecx, ch
      !sub eax, ecx
      !imul eax, eax
      !shl eax, 11
      !cmp eax, [p.v_bestd]
      !jnc nearestcolor.findnearest#i#_cont1
      !mov [p.v_d], eax
      !movzx eax, byte [p.v_Color]
      !movzx ecx, byte [p.v_c#i#]
      !lea edx, [eax + ecx]   ; edx = rsum
      !sub eax, ecx
      !imul eax, eax          ; eax = r*r
      !lea ecx, [edx + 0x400] ; ecx = $400 + rsum
      !imul eax, ecx          ; eax = ($400+rsum)*r*r
      !add [p.v_d], eax
      !movzx eax, byte [p.v_Color + 2]
      !movzx ecx, byte [p.v_c#i# + 2]
      !sub eax, ecx
      !imul eax, eax          ; eax = b*b
      !neg edx
      !add edx, 0x5fe         ; edx = $5fe - rsum
      !imul eax, edx          ; eax = ($5fe-rsum)*b*b
      !add eax, [p.v_d]
      !cmp eax, [p.v_bestd]
      !jnc nearestcolor.findnearest#i#_cont0
      !mov [p.v_bestd], eax
      !mov eax, [p.v_c#i#]
      !mov [p.v_c], eax
      !nearestcolor.findnearest#i#_cont0:
      mov rdx, *p#i
      add rdx, st
      mov *p#i, rdx
      mov eax, [rdx]
      !mov [p.v_c#i#], eax
      CompilerIf i = 1
         !jmp nearestcolor.findnearest0_loop
      CompilerElse
         !jmp nearestcolor.findnearest1_loop
      CompilerEndIf
      !nearestcolor.findnearest#i#_cont1:
      !mov dword [p.v_c#i#], 0
      !nearestcolor.findnearest#i#_cont2:
      CompilerIf i = 1
         !cmp dword [p.v_c0], 0
         !jnz nearestcolor.findnearest0_loop
      CompilerEndIf
   EndMacro
   
   Procedure.l FindNearest(Color.l)
      ; Find the nearest color
      Protected.l c, c0, c1, d, bestd = $12000000
      Protected.Long *p0, *p1
      !movzx eax, byte [p.v_Color + 1]
      !mov [p.v_d], eax
      *p1 = @Palette(IndexG(d)) : *p0 = *p1 - 4
      c0 = *p0\l : c1 = *p1\l
      M_FindNearest(0, -4)
      M_FindNearest(1, 4)
      ProcedureReturn c
   EndProcedure
   
   Macro M_DitherImage(offset, n = 1)
      !movsx ecx, byte [p.v_err + offset]
      CompilerIf n
         !movsx eax, byte [p.v_err50 + offset]
         !add ecx, eax
      CompilerEndIf
      !imul ecx, edx
      !sar ecx, 8
      !movzx eax, byte [p.v_c0 + offset]
      !add eax, [p.v_badd]
      !imul eax, [p.v_cmul]
      !sar eax, 8
      !lea eax, [eax + ecx + 128]
      !neg ah
      !setz cl
      !neg cl
      !and al, cl
      !sar ah, 7
      !or al, ah
      !mov [p.v_c0 + offset], al
   EndMacro
   
   Procedure.i DitheredImage(Image.i, DitherLevel.a = 220)
      ; Return a dithered image
      ; DitherLevel : 0 - 255
      Protected.i result, x, y, w, h, cmul.l = 256, badd.l = -128
      Protected.l c0, c1, err50, err
      result = CopyImage(Image, #PB_Any)
      If result And StartDrawing(ImageOutput(result))
            h = OutputHeight()
            w = OutputWidth()
            If DitherLevel = 0
               While y < h
                  x = 0
                  While x < w
                     Plot(x, y, FindNearest(Point(x, y)))
                     x + 1
                  Wend
                  y + 1
               Wend
            Else
               Dim d_error.l(w)
               While y < h
                  x = 0 : err50 = 0
                  While x < w
                     c0 = Point(x, y)
                     ; add previous error
                     err = d_error(x)
                     !movzx edx, byte [p.v_DitherLevel]
                     M_DitherImage(0)
                     M_DitherImage(1)
                     M_DitherImage(2)
                     c1 = FindNearest(c0)
                     Plot(x, y, c1)
                     ; calculate 50% error
                     !mov eax, [p.v_c0]
                     !mov ecx, [p.v_c1]
                     !mov edx, eax
                     !not edx
                     !and edx, ecx
                     !and edx, 0x01010101
                     !or eax, 0x01010101
                     !and ecx, 0xfefefefe
                     !sub eax, ecx
                     !xor eax, 0x01010101
                     !shr eax, 1
                     !sub eax, edx
                     !mov ecx, [p.v_err50]
                     !mov [p.v_err50], eax
                     ; mix with previous error
                     !xor eax, 0x80808080
                     !xor ecx, 0x80808080
                     !mov edx, eax
                     !and edx, ecx
                     !and edx, 0x01010101
                     !and eax, 0xfefefefe
                     !and ecx, 0xfefefefe
                     !add eax, ecx
                     !shr eax, 1
                     !add eax, edx
                     !xor eax, 0x80808080
                     !mov [p.v_err], eax
                     d_error(x) = err
                     x + 1
                  Wend
                  d_error(0) << 1
                  y + 1
               Wend
            EndIf
         StopDrawing()
      EndIf
      ProcedureReturn result
   EndProcedure
   
EndModule

; Gaussian procedures by wilbert:
; http://www.purebasic.fr/english/viewtopic.php?f=12&t=55844
; Gaussian (requires MMX)
Procedure Gaussian1D(*PixelBuf32, NumPixels, NextPixelOffset = 4)
   
   ; Matrix : 1 - 4 - 6 - 4 - 1 (/16)
   
   ; exit if NumPixels < 3
   !mov ecx, [p.v_NumPixels]
   !sub ecx, 3
   !js gaussian1d_exit
   !mov eax, [p.v_NextPixelOffset]  
   
   ; initial setup
   CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
      !mov rdx, [p.p_PixelBuf32]
      !movd mm1, [rdx]
      !movd mm4, [rdx + rax]
   CompilerElse
      !mov edx, [p.p_PixelBuf32]
      !movd mm1, [edx]
      !movd mm4, [edx + eax]
   CompilerEndIf
   !pxor mm7, mm7
   !pxor mm6, mm6
   !punpcklbw mm1, mm6
   !punpcklbw mm4, mm6
   !movq mm2, mm1
   !movq mm3, mm1
   
   ; loop
   !gaussian1d_loop0:
   CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
      !movd mm5, [rdx + rax * 2]
   CompilerElse
      !movd mm5, [edx + eax * 2]
   CompilerEndIf
   !punpcklbw mm5, mm6
   !gaussian1d_loop1:
   !movq mm0, mm2
   !paddw mm0, mm3
   !paddw mm0, mm4
   !psllw mm0, 2
   !paddw mm0, mm1
   !paddw mm0, mm3
   !paddw mm0, mm3
   !paddw mm0, mm5
   !paddw mm0, mm7
   !movq mm7, mm0
   !psraw mm0, 4
   !packuswb mm0, mm0
   CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
      !movd [rdx], mm0
      !add rdx, rax
   CompilerElse
      !movd [edx], mm0
      !add edx, eax
   CompilerEndIf
   !movq mm1, mm2
   !movq mm2, mm3
   !movq mm3, mm4
   !movq mm4, mm5
   !punpcklbw mm0, mm6
   !psllw mm0, 4
   !psubw mm7, mm0
   !dec ecx
   !jns gaussian1d_loop0
   !cmp ecx, -3
   !jne gaussian1d_loop1
   !gaussian1d_exit:
   !emms
   
EndProcedure

Procedure Gaussian2D(*PixelBuf32, Width, Height, BufferPitch = 0)
   
   ; 1 -  4 -  6 -  4 - 1
   ; 4 - 16 - 24 - 16 - 4
   ; 6 - 24 - 36 - 24 - 4
   ; 4 - 16 - 24 - 16 - 4
   ; 1 -  4 -  6 -  4 - 1
   
   If BufferPitch = 0
      BufferPitch = Width << 2
   EndIf
   
   Protected i.i = 0
   While i < Height
      Gaussian1D(*PixelBuf32 + BufferPitch * i, Width)
      i + 1  
   Wend
   
   i = 0
   While i < Width
      Gaussian1D(*PixelBuf32 + i << 2, Height, BufferPitch)
      i + 1  
   Wend
   
EndProcedure

Procedure GaussianBlur(Image, Strength = 0)
   
   Protected.i i, w, h, x, y, max_x, max_y
   
   If StartDrawing(ImageOutput(Image))
         w = OutputWidth()
         h = OutputHeight()
         
         If DrawingBufferPixelFormat() & $60; 32 bit buffer ?
            For i = 0 To Strength
               Gaussian2D(DrawingBuffer(), w, h, DrawingBufferPitch())
            Next
         Else
            max_x = w - 1
            max_y = h - 1
            Dim Buffer.l(max_y, max_x)
            DrawingMode(#PB_2DDrawing_AllChannels)
            For y = 0 To max_y
               For x = 0 To max_x
                  Buffer(y, x) = Point(x, y)
               Next
            Next
            For i = 0 To Strength
               Gaussian2D(@Buffer(), w, h)
            Next
            For y = 0 To max_y
               For x = 0 To max_x
                  Plot(x, y, Buffer(y, x))
               Next
            Next
         EndIf
      StopDrawing()
      
   EndIf
   
EndProcedure



;- main block
;{ following code by BasicallyPure
Declare.i HIDE_IMAGE_IN_IMAGE(containerImage, hiddenImage)
Declare.i LOAD_IMAGE(image.i)
Declare.i PASSWORD_REQUESTER(text.s)
Declare.i REFRESH_IMAGE_WIN(message.s, IsNewImage.i = 0)
Declare.i SAVE_IMAGE(image.i)
Declare.i TOGGLE(image.i)
Declare.i TRIM_EXTRACTED_IMAGE()
Declare.i UNHIDE_IMAGE(image.i)

Global File$, invisible.i, currentImage.i
Define.i x, y, quit = #False

CompilerIf #PB_Compiler_OS = #PB_OS_Linux
   File$ = GetHomeDirectory() + "Pictures/"
CompilerEndIf

Enumeration
   #imgContainer : #imgSmContainer : #imgGadContainer
   #imgInsertion : #imgSmInsertion : #imgGadHidden
   #imgComposite : #imgSmComposite : #imgGadComposite
   #imgExtracted : #imgSmExtracted : #imgGadExtracted
   #btnCopy      : #btnLoad_I : #btnExtract : #BtnLoad_M
   #btnMerge     : #btnLoad_C : #imgGadMain : #BtnSave_M
   #strMessage   : #btnPaste  : #imgTemp    : #CheckBoxPassword
   #CheckBoxTrim
EndEnumeration

; container gadgets & their images share the same numbers
#cont_C = #imgContainer
#cont_I = #imgInsertion
#cont_M = #imgComposite
#cont_E = #imgExtracted

currentImage = #imgContainer

If Not OpenWindow(0,0,0,1,1,"",#PB_Window_BorderLess|#PB_Window_Invisible) : End : EndIf
invisible = #True

ImageGadget(#imgGadMain,0,0,1,1,0)

#bakColor = $F9F1CC
#actColor = $A2FFFD

If OpenWindow(1,0,0,450,250,"Hide Image in Image 2.1",#PB_Window_ScreenCentered|#PB_Window_SystemMenu)
   SetWindowColor(1,#bakColor)
   
   StringGadget(#strMessage, 005,000,435,30,"",#PB_String_ReadOnly)
   If LoadFont(1,"Arial",12) : SetGadgetFont(#strMessage, FontID(1)) : EndIf
   If LoadFont(2,"Arial",10) : SetGadgetFont(#PB_Default, FontID(2)) : EndIf
      
   ButtonGadget(#btnLoad_C,  005,035,100,35,"Load container") : GadgetToolTip(#btnLoad_C,"Load container image")
   ButtonGadget(#btnLoad_I,  005,085,100,35,"Load insertion") : GadgetToolTip(#btnLoad_I,"Load image to hide")
   ButtonGadget(#btnMerge,   005,130,100,35,"Merge images")   : GadgetToolTip(#btnMerge,"Create composite with hidden image")
   ButtonGadget(#btnExtract, 005,175,100,35,"Extract")        : GadgetToolTip(#btnExtract,"extract hidden from composite")
   ButtonGadget(#BtnLoad_M,  335,035,105,35,"Load composite") : GadgetToolTip(#BtnLoad_M, "load a composite image")
   ButtonGadget(#BtnSave_M,  335,085,105,35,"Save composite") : GadgetToolTip(#BtnSave_M, "save merged images")
   ButtonGadget(#btnCopy,    335,130,105,35,"Copy")           : GadgetToolTip(#btnCopy, "copy image to clipboard")
   ButtonGadget(#btnPaste,   335,175,105,35,"Paste")          : GadgetToolTip(#btnPaste,"paste image from clipboard")
   
   ContainerGadget(#cont_C,115,035,104,85) : SetGadgetColor(#cont_C,#PB_Gadget_BackColor ,#actColor)
      FrameGadget(#PB_Any,           0,0,104,085,"container")
      ImageGadget(#imgGadContainer,  10,20,084,56,0,#PB_Image_Border)
   CloseGadgetList()
    
   ContainerGadget(#cont_I,115,125,104,85) : SetGadgetColor(#cont_I,#PB_Gadget_BackColor ,#bakColor)
      FrameGadget(#PB_Any,           0,0,104,085,"insertion")
      ImageGadget(#imgGadHidden,     10,20,084,56,0,#PB_Image_Border)
   CloseGadgetList()

   ContainerGadget(#cont_M,225,035,104,85) : SetGadgetColor(#cont_M,#PB_Gadget_BackColor ,#bakColor)
      FrameGadget(#PB_Any,           0,0,104,085,"composite")
      ImageGadget(#imgGadComposite,  10,20,084,56,0,#PB_Image_Border)
   CloseGadgetList()
   
   ContainerGadget(#cont_E,225,125,104,85) : SetGadgetColor(#cont_E,#PB_Gadget_BackColor ,#bakColor)
      FrameGadget(#PB_Any,           0,0,104,085,"extracted")
      ImageGadget(#imgGadExtracted,  10,20,084,56,0,#PB_Image_Border)
   CloseGadgetList()
   
   CheckBoxGadget(#CheckBoxPassword,5,215,215,30," Enable password option.")
   SetGadgetState(#CheckBoxPassword,#False)
   
   CheckBoxGadget(#CheckBoxTrim,225,215,215,30," Trim extracted image.")
   SetGadgetState(#CheckBoxTrim,#False)
   
   DisableGadget(#BtnSave_M,1)
   DisableGadget(#btnCopy,1)
   DisableGadget(#btnMerge,1)
   DisableGadget(#btnExtract,1)
   
   ;-Event loop
   ;{
   Repeat
      Select WaitWindowEvent()
         Case #PB_Event_CloseWindow : quit = #True
            
         Case #PB_Event_ActivateWindow
            If EventWindow() = 1
               StickyWindow(0,1) : StickyWindow(0,0)
               StickyWindow(1,1) : StickyWindow(1,0)
            EndIf
            
         Case #PB_Event_Gadget
            Select EventGadget()
               Case #imgGadMain
                  If EventType() = #PB_EventType_LeftClick : SetActiveWindow(1) : EndIf
                  
               Case #btnLoad_C : ; Load container image
                  If LOAD_IMAGE(#imgContainer)
                     REFRESH_IMAGE_WIN("Container image was loaded from disk.", 1)
                  Else
                     SetGadgetText(#strMessage,"No image was loaded.")
                  EndIf
                  
               Case #btnLoad_I : ; Load image to hide
                  If LOAD_IMAGE(#imgInsertion)
                     REFRESH_IMAGE_WIN("Image to hide was loaded from disk.", 1)
                  Else
                     SetGadgetText(#strMessage,"No image was loaded.")
                  EndIf
                  
               Case #BtnLoad_M ; load composite image
                  If LOAD_IMAGE(#imgComposite)
                     REFRESH_IMAGE_WIN("Composite image was loaded from disk.", 1)
                  Else
                     SetGadgetText(#strMessage,"No image was loaded.")
                  EndIf
                  
               Case #BtnSave_M ; save composite image
                  If SAVE_IMAGE(#imgComposite)
                     SetGadgetText(#strMessage,"Composite image was saved.")
                  Else
                     SetGadgetText(#strMessage,"Error! Image was not saved.")
                  EndIf
                  
               Case #btnCopy ; Copy to clipboard
                  If IsImage(currentImage)
                     SetClipboardImage(currentImage)
                     SetGadgetText(#strMessage,"Image copied to clipboard.")
                  Else
                     SetGadgetText(#strMessage,"Error!  no image available")
                  EndIf
                  
               Case #btnPaste ;Paste into currentImage
                  If GetClipboardImage(#imgTemp)
                     CopyImage(#imgTemp,currentImage)
                     If invisible : HideWindow(0,0) : SetActiveWindow(1) : invisible = #False : EndIf
                     REFRESH_IMAGE_WIN("Image pasted from clipboard.", 1)
                     TOGGLE(currentImage)
                  Else : SetGadgetText(#strMessage,"Error!  No image in clipboard.")
                  EndIf
                  
               Case #btnMerge ; insert #imgInsertion into #imgContainer
                  If PASSWORD_REQUESTER("Password to secure this image?")
                     CopyImage(#imgContainer, #imgComposite)
                     If HIDE_IMAGE_IN_IMAGE(#imgComposite,#imgInsertion)
                        TOGGLE(#imgComposite)
                        REFRESH_IMAGE_WIN("Hidden image has been inserted.", 1)
                     EndIf
                  EndIf
                  
               Case #btnExtract ; reveal any hidden image in #imgComposite
                  If PASSWORD_REQUESTER("Password to reveal the hidden image?")
                     If IsImage(#imgComposite)
                        CopyImage(#imgComposite, #imgExtracted)
                        UNHIDE_IMAGE(#imgExtracted)
                        TOGGLE(#imgExtracted)
                        REFRESH_IMAGE_WIN("Extraction process complete", 1)
                     EndIf
                  EndIf
                  
               Case #imgGadContainer : TOGGLE(#imgContainer)
                  If IsImage(#imgContainer)
                     REFRESH_IMAGE_WIN("Container image displayed.")
                  Else
                     SetGadgetText(#strMessage,"Error!  No container image available.")
                  EndIf
                  
               Case #imgGadComposite : TOGGLE(#imgComposite)
                  If IsImage(#imgComposite)
                     REFRESH_IMAGE_WIN("Composite image displayed.")
                  Else
                     SetGadgetText(#strMessage,"Error!  No composite image available.")
                  EndIf
                  
               Case #imgGadHidden : TOGGLE(#imgInsertion)
                  If IsImage(#imgInsertion)
                     REFRESH_IMAGE_WIN("Insertion image displayed.")
                  Else
                     SetGadgetText(#strMessage,"Error!  No insertion image available.")
                  EndIf
                  
               Case #imgGadExtracted : TOGGLE(#imgExtracted)
                  If IsImage(#imgExtracted)
                     REFRESH_IMAGE_WIN("Extracted image displayed.")
                  Else
                     SetGadgetText(#strMessage,"Error!  No extracted image available.")
                  EndIf
                  
               Case #CheckBoxTrim
                  If GetGadgetState(#CheckBoxTrim)
                     If TRIM_EXTRACTED_IMAGE()
                        REFRESH_IMAGE_WIN("Extracted image trimmed.",1)
                     EndIf
                  ElseIf IsImage(#imgExtracted)
                     PostEvent(#PB_Event_Gadget,1,#btnExtract)
                  EndIf
                  
            EndSelect
      EndSelect
   Until quit : ;}
EndIf

End : ;}

Procedure HIDE_IMAGE_IN_IMAGE(containerImage, hiddenImage)
   ; The hiddenImage will be embedded in the containerImage.
   ; If hiddenImage is larger than containerImage then it will shrink to fit.
   ; The LSB of the containerImage pixels are used to store the hidden
   ; image information in 8-color format & encrypted using XOR with random bits.
   ; This procedure depends upon the NearestColor module
   ; Assumes the RandomSeed() has been properly set by PASSWORD_REQUESTER().
   
   Protected Xmax, Ymax, ix, iy, result, dithered_image, container_Aspect.f, hidden_Aspect.f
   Protected x, y, containerWidth, hiddenWidth, containerHeight, hiddenHeight
   
   If IsImage(containerImage) And IsImage(hiddenImage)
      
      containerWidth  = ImageWidth(containerImage)  : hiddenWidth  = ImageWidth(hiddenImage)
      containerHeight = ImageHeight(containerImage) : hiddenHeight = ImageHeight(hiddenImage)
      
      Xmax = containerWidth  - 1
      Ymax = containerHeight - 1
      
      ; all container image pixel LSBs are set to random values
      StartDrawing(ImageOutput(containerImage))
         For iy = 0 To Ymax
            For ix = 0 To Xmax
               Plot(ix, iy, (Point(ix,iy) & $FEFEFE) | (Random($FFFFFF) & $010101))
            Next ix
         Next iy
      StopDrawing()
      ;
      
      CopyImage(hiddenImage,#imgTemp)
      
      ; resize hiddenImage to fit container if needed
      If hiddenWidth > containerWidth Or hiddenHeight > containerHeight
         hidden_Aspect = hiddenWidth    / hiddenHeight
         container_Aspect = containerWidth / containerHeight
         
         If hidden_Aspect > container_Aspect ; shirnk to fit x
            ResizeImage(#imgTemp, containerWidth, containerWidth / hidden_Aspect)
         Else ;shrink to fit y
            ResizeImage(#imgTemp, containerHeight * hidden_Aspect, containerHeight)
         EndIf
      EndIf
      
      ; reduce the hiddenImage to 8 colors with dithering
      dithered_image = NearestColor::DitheredImage(#imgTemp)
      
      Xmax = ImageWidth(dithered_image) - 1
      Ymax = ImageHeight(dithered_image) - 1
      
      If IsImage(dithered_image)
         
         ; temporary storage of the ditheredImage information
         Dim _8colorInfo(Xmax,Ymax)
         StartDrawing(ImageOutput(dithered_image))
            For iy = 0 To Ymax
               For ix = 0 To Xmax
                  _8colorInfo(ix,iy) = Point(ix,iy) & $010101
               Next ix
            Next iy
         StopDrawing()
         ;
         
         ;position of hidden image
         x = (containerWidth  - ImageWidth(dithered_image))  / 2
         y = (containerHeight - ImageHeight(dithered_image)) / 2
         If x < 0 : x = 0 : EndIf
         If y < 0 : y = 0 : EndIf
         ;
         
         FreeImage(dithered_image)
         FreeImage(#imgTemp)
         
         Xmax + x : Ymax + y
         If Xmax >= containerWidth  : Xmax = containerWidth  - 1 : EndIf
         If Ymax >= containerHeight : Ymax = containerHeight - 1 : EndIf
         
         StartDrawing(ImageOutput(containerImage))
            ; embed the hiddenImage into the containerImage
            For iy = y To Ymax
               For ix = x To Xmax
                  Plot(ix,iy, Point(ix,iy) ! _8colorInfo(ix-x,iy-y)) ; XOR encryption
               Next ix
            Next iy
         StopDrawing()
         
         FreeArray(_8colorInfo())
         result = #True
      EndIf
   EndIf
   
   ProcedureReturn result
EndProcedure

Procedure LOAD_IMAGE(image.i)
   Static Pattern$ = "*.jpg|*.jpg|*.png|*.png|*.bmp|*.bmp|*.jpg, *.png, *.bmp|*.jpg;*.png;*.bmp"
   Static Pattern = 3
   Protected result
   
   File$ = OpenFileRequester("Select an image", File$, Pattern$, Pattern)
   If File$
      Pattern = SelectedFilePattern()
      If LoadImage(image, File$)
         If invisible : HideWindow(0,0) : SetActiveWindow(1) : invisible = #False : EndIf
         TOGGLE(image)
      EndIf
      result = #True
   Else
      SetGadgetText(#strMessage,"No image was loaded.")
   EndIf
   ProcedureReturn result
EndProcedure

Procedure PASSWORD_REQUESTER(text.s)
   Protected  i.i, seed.q, password.s, result.i
   Static oldPassword.s
   
   If GetGadgetState(#CheckBoxPassword)
      password = oldPassword
      password = InputRequester("Password Requester", text, password)
      If password : oldPassword = password : EndIf
   Else
      password = "gorewag dole's alihap estoa" ; <-- the default password
   EndIf
   
   If password
      For i = 1 To Len(password)
         seed = seed<<1 ! Asc(Mid(password,i))
      Next
      RandomSeed(seed) 
      result = #True
   Else
      SetGadgetText(#strMessage,"Operation was cancelled.")
   EndIf
   
   ProcedureReturn result
EndProcedure

Procedure REFRESH_IMAGE_WIN(message.s, IsNewImage.i = 0)
   ResizeWindow(0,#PB_Ignore,#PB_Ignore,ImageWidth(currentImage),ImageHeight(currentImage))
   SetGadgetState(#imgGadMain,ImageID(currentImage))
   SetGadgetText(#strMessage,message)
   HideWindow(0,0,#PB_Window_ScreenCentered)
   
   If IsNewImage
      CopyImage(currentImage, currentImage+1)
      ResizeImage(currentImage+1,84,56)
      SetGadgetState(currentImage+2,ImageID(currentImage+1))
   EndIf
   
EndProcedure

Procedure SAVE_IMAGE(image.i)
   Static YesNo = #PB_MessageRequester_YesNo, Yes = #PB_MessageRequester_Yes
   Static Pattern$ = "image.png|*.png|image.bmp|*.bmp"
   Static pattern = 0, lastFileName.s = "FileName"
   Protected F$, p, result, cancel = #False
   
   Select LCase(GetExtensionPart(File$))
      Case "png" : pattern = 0
      Case "bmp" : pattern = 1
   EndSelect
   
   F$ = SaveFileRequester("Save composite image",  GetPathPart(File$)+lastFileName, Pattern$, pattern)
   
   If F$
      pattern = SelectedFilePattern()
      lastFileName = GetFilePart(F$,#PB_FileSystem_NoExtension)
      
      ; remove any existing file extension
      p = FindString(ReverseString(F$),".")
      If p
         F$ = Left(F$,Len(F$)-p)
      EndIf
      
      Select SelectedFilePattern()
         Case 0 : F$ + ".png"
         Case 1 : F$ + ".bmp"
      EndSelect
      
      If FileSize(F$) <> -1 ; file exists
         If MessageRequester("File Exists!", "Do you wish to overwrite?", YesNo) <> Yes
            cancel = #True
         EndIf
      EndIf
      
      If cancel = #False
         Select SelectedFilePattern()
            Case 0 : result = SaveImage(#imgComposite, F$, #PB_ImagePlugin_PNG)
            Case 1 : result = SaveImage(#imgComposite, F$, #PB_ImagePlugin_BMP)
         EndSelect
         
         File$ = F$ ; for global use
      EndIf
   EndIf
   
   ProcedureReturn result
EndProcedure

Procedure TOGGLE(image.i)
   ; highlight the active image container
   SetGadgetColor(currentImage,#PB_Gadget_BackColor ,#bakColor)
   currentImage = image
   SetGadgetColor(currentImage,#PB_Gadget_BackColor ,#actColor)
   
   If IsImage(#imgInsertion) And IsImage(#imgContainer)
      DisableGadget(#btnMerge,0)
   Else
      DisableGadget(#btnMerge,1)
   EndIf
   
   If currentImage = #imgExtracted
      DisableGadget(#btnPaste,1)
   Else
      DisableGadget(#btnPaste,0)
   EndIf
   
   If IsImage(currentImage)
      DisableGadget(#btnCopy,0)
   Else
      DisableGadget(#btnCopy,1)
   EndIf
   
   If IsImage(#imgComposite)
      DisableGadget(#btnExtract,0)
      DisableGadget(#BtnSave_M,0)
   EndIf
EndProcedure

Procedure TRIM_EXTRACTED_IMAGE()
   ;remove the black borders around the extracted image
   Protected x, y, w, h, Xmax, Ymax, xb, yb, result
   
   If IsImage(#imgExtracted)
      w = ImageWidth(#imgExtracted)
      h = ImageHeight(#imgExtracted)
      Xmax = w - 1 : xb = Xmax
      Ymax = h - 1 : yb = Ymax
      
      ;find the borders
      StartDrawing(ImageOutput(#imgExtracted))
         For y = 0 To Ymax
            For x = 0 To Xmax
               If Point(x,y)
                  If x < xb : xb = x : EndIf
                  If y < yb : yb = y : EndIf
               EndIf
            Next x
         Next y
      StopDrawing()
   
      w = w - xb*2 : h = h - yb*2
      
      If GrabImage(#imgExtracted, #imgExtracted, xb, yb, w, h)
         TOGGLE(#imgExtracted)
         result = #True
      EndIf
      
   EndIf
   
   ProcedureReturn result
EndProcedure

Procedure UNHIDE_IMAGE(image)
   ; If an embeded encrypted image exists this will reveal it.
   ; This procedure depends upon the GaussianBlur() procedure.
   ; Assumes the RandomSeed() has been properly set by PASSWORD_REQUESTER().
   
   Protected x, y, c, r, g, b
   Protected Xmax = ImageWidth(image) - 1, Ymax = ImageHeight(image) - 1
   
   If IsImage(image)
      
      StartDrawing(ImageOutput(image))
         For y = 0 To Ymax
            For x = 0 To Xmax
               c = Point(x,y) ! (Random($FFFFFF) & $010101)
               r = (c & $000001) * $FF
               g = (c & $000100) * $FF
               b = (c & $010000) * $FF
               Plot(x, y, b | g | r)
            Next x
         Next y
      StopDrawing()
      
      GaussianBlur(image,0)
      
      If GetGadgetState(#CheckBoxTrim)
         TRIM_EXTRACTED_IMAGE()
      EndIf
      
   EndIf
EndProcedure
Last edited by BasicallyPure on Sat Sep 30, 2017 8:56 am, edited 5 times in total.
BasicallyPure
Until you know everything you know nothing, all you have is what you believe.
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Re: Hide image in image

Post by Keya »

ive seen 'standard steganography' ones before where the image is embedded (LSB or 2LSB etc) as-is/losslessly, i think walbus has a few demos of that, but i see your method reduces the image to 8bit, dithers, and then blurs the result, so please forgive my confusion but it seems like more work to produce a poorer-quality image? what are the main reasons/advantages?

on another note im stunned how relatively well the Gaussian Blur 'restored' the 8bit pic :)
maybe also add a Sharpen effect after? here's a 3x3 convolution matrix for Sharpen:
0, -1, 0
-1, 5, -1
0, -1, 0
I notice youre already using 5x5 with Gaussian so you can probably plug n play :) i just checked and the 5x5 is the same:
Image
User avatar
BasicallyPure
Enthusiast
Enthusiast
Posts: 536
Joined: Thu Mar 24, 2011 12:40 am
Location: Iowa, USA

Re: Hide image in image

Post by BasicallyPure »

Hi,
I haven't seen the ones you refer to but it doesn't seem possible to embed losslessly an image the same size as the container.
I thought about using 2LSB but that might start to become visible in the container image, I don't know.
Keya wrote:what are the main reasons/advantages?
If it can be done losslessly then there are no advantages.
I just thought of a way and wanted to see if I could get it to work.
BasicallyPure
Until you know everything you know nothing, all you have is what you believe.
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Re: Hide image in image

Post by Keya »

or maybe standard compression with lzma or whatever might also be able to reduce the image down to a similar size as the 8-color without quality loss
walbus
Addict
Addict
Posts: 929
Joined: Sat Mar 02, 2013 9:17 am

Re: Hide image in image

Post by walbus »

It is as keya say
But, primary, a LSB content you can not hidden complete to (simple) picture analysis.
Primary i can found ever artefacts in this pictures, headers, extenders, contents
Last edited by walbus on Fri Dec 30, 2016 8:27 pm, edited 4 times in total.
Fred
Administrator
Administrator
Posts: 16621
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: Hide image in image

Post by Fred »

It's very cool code !
walbus
Addict
Addict
Posts: 929
Joined: Sat Mar 02, 2013 9:17 am

Re: Hide image in image

Post by walbus »

This is right, a very cool code
But it is nothing steganographic hidden :wink:
Last edited by walbus on Sat Dec 31, 2016 1:03 pm, edited 2 times in total.
davido
Addict
Addict
Posts: 1890
Joined: Fri Nov 09, 2012 11:04 pm
Location: Uttoxeter, UK

Re: Hide image in image

Post by davido »

@BasicallyPure,
Nice work. 8)
Thank you for sharing.
DE AA EB
User avatar
BasicallyPure
Enthusiast
Enthusiast
Posts: 536
Joined: Thu Mar 24, 2011 12:40 am
Location: Iowa, USA

Re: Hide image in image

Post by BasicallyPure »

This method came about when I was working on adding new features to my CQ_ImageEditor.
(no, I haven't updated it yet)
I made the observation that if an image was reduced to just 8 basic colors and dithered with the Sierra Lite method
then a gaussian blur would result in a good approximation of the original image.
Only the Sierra Lite dithering method works well for this, other dithering methods are not so good.
Because the 8 basic colors; black, white, red, green, blue, cyan, magenta, & yellow, can be stored in only 3 bits it works well for LSB insertion.

It was not my intention to produce a secure encryption of the hidden image.
If that is desired I would suggest encrypting the temporary array that holds the 8-color information.
BasicallyPure
Until you know everything you know nothing, all you have is what you believe.
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5342
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Hide image in image

Post by Kwai chang caine »

Amazing code for the new year !!!! :shock:
A big 007 tool for FBI secret agent :mrgreen:
And the nice girl hide very well his secret :lol:
Works very well, thanks a lot for share it 8)
ImageThe happiness is a road...
Not a destination
davido
Addict
Addict
Posts: 1890
Joined: Fri Nov 09, 2012 11:04 pm
Location: Uttoxeter, UK

Re: Hide image in image

Post by davido »

@BasicallyPure,
Works fine on my MacBook Pro. :D

One very minor point: The text, on the Load Insertion and Load Container buttons, is slightly truncated.
I simply increased the width to 110 pixels.

Pure Magic - thank you.
DE AA EB
User avatar
BasicallyPure
Enthusiast
Enthusiast
Posts: 536
Joined: Thu Mar 24, 2011 12:40 am
Location: Iowa, USA

Re: Hide image in image

Post by BasicallyPure »

@davido,
Thank you for the report about the MacBook.
davido wrote:One very minor point: The text, on the Load Insertion and Load Container buttons, is slightly truncated.
I found the same problem with button text when I tested on Linux after I developed the program while using Windows.
It's one of the biggest headaches with cross platform code, especially if you don't happen to have the hardware to test everything.
BasicallyPure
Until you know everything you know nothing, all you have is what you believe.
infratec
Always Here
Always Here
Posts: 6817
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Hide image in image

Post by infratec »

Hi,

I think the main point for crossplatform is the size of the default font.
It differs.

An easy fix:
Always load a font and set it as default.

This worked for me.

Bernd
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Re: Hide image in image

Post by Keya »

infratec wrote:An easy fix: Always load a font and set it as default.
i like it! sounds a lot easier to work with than my current 'solution' of one .pbf for each OS, lol
What's a good and legally unimpeded font to use across all three OS?
davido
Addict
Addict
Posts: 1890
Joined: Fri Nov 09, 2012 11:04 pm
Location: Uttoxeter, UK

Re: Hide image in image

Post by davido »

@BasicallyPure,
I always use the Dialog Library. This almost always removes the problems with gadgets having truncated text on the Mac.
For me this works out fine, however, others may have a different view.
DE AA EB
Post Reply