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