Coded Images - TerryRitchie - 02-23-2024
Here are a few routines I wrote to store and retrieve a string that can be embedded within an image.
Think of this as writing a description on the back of a photograph. The image itself will contain its own description.
The embedded string can be anything; text, another image, even another file. There are limits on the string size though (see comments in code).
The code below includes sample code that will load 9 photos that have already had string embedded into them. The photos will display a caption that was decoded from the embedded string. The 9 photos are in the ZIP file below.
This code is just a proof on concept for me. It's highly inefficient but extremely fast. Go ahead and see what you can do, or modify, with it.
Code: (Select All) '
' Encode/Decode Routines
' by Terry Ritchie 02/23/24
'
'+-----------------------------------------------------------------------------------------------+
'| Routines to encode and decode a string within an image. |
'| |
'| Possible uses: |
'| |
'| - Embed a description of the image into the image, like writing on the back of a photograph. |
'| - Secret spy stuff, pass messages embedded in images to friends. |
'| - Embed another file or image within an image. |
'| |
'| Notes: |
'| |
'| - These routines only work with 32bit images. |
'| - It's best to use images that are opague (all alpha values = 255 such as photographs) |
'| - The formula to determine the maximum string length an image can hold is: |
'| |
'| String_Size = (Image_Width x Image_Height \ 8) - 6 |
'| |
'+-----------------------------------------------------------------------------------------------+
OPTION _EXPLICIT ' declare those variables!
CONST SWIDTH = 800 ' screen width
CONST SHEIGHT = 600 ' screen height
DIM Image AS LONG ' image to load
DIM DecodedText AS STRING ' decoded string from image
DIM p AS INTEGER ' photo counter
'**
'** BEGIN EXAMPLE CODE: cycle through 9 photos that have encoded strings within them
'**
SCREEN _NEWIMAGE(SWIDTH, SHEIGHT, 32)
FOR p = 1 TO 9
Image = _LOADIMAGE("Photo" + _TRIM$(STR$(p)) + ".png", 32) ' load the image
DecodedText = DecodeImage(Image) ' decode string inside image
Display_Image Image, DecodedText, 1 ' show image and decoded caption
_FREEIMAGE Image ' free the image
NEXT p
SYSTEM
SUB Display_Image (i AS LONG, s AS STRING, p AS INTEGER)
CLS
_PUTIMAGE ((SWIDTH - _WIDTH(i)) \ 2, (SHEIGHT - _HEIGHT(i)) \ 2), i
LOCATE 2, ((SWIDTH \ 8) - LEN(s)) \ 2
PRINT s;
IF p THEN
LOCATE (SHEIGHT \ 16) - 1, ((SWIDTH \ 8) - 11) \ 2
PRINT "PRESS A KEY";
SLEEP
END IF
END SUB
'**
'** END EXAMPLE CODE
'**
'**
'** EXAMPLE: Saving an entire file within an image.
'**
'ff = FREEFILE ' get a free file handle
'OPEN "readme.md" FOR BINARY AS #ff ' open text file
'Text$ = SPACE$(LOF(ff)) ' create a string the size of file
'GET #ff, , Text$' get text as one string
'CLOSE #ff ' close text file
'Image& = _LOADIMAGE("photo.png", 32) ' load image to encode string into
'EncodedImage& = EncodeImage(Image&, Text$) ' encode string within image
'_SAVEIMAGE "EncodedPhoto.png", EncodedImage& ' save encoded image (use a non lossy format!)
'_FREEIMAGE Image& ' remove image from memory
'_FREEIMAGE EncodedImage& ' remove encoded image from memory
' ______________________________________________________________________________________________________
'/ \
FUNCTION EncodeImage& (i AS LONG, st AS STRING) ' EncodeImage& |
' __________________________________________________________________________________________________|____
'/ \
'| Embeds a string within an image. |
'| |
'| i - image to encode |
'| st - string to encode within image |
'| |
'| Returns: handle of new encoded image (-2 or less) |
'| -1 if an error occurred |
'| |
'| Note: Only works with 32 bit images |
'| |
'| The string is converted to a series of 1's and 0's that will be used to determine the alpha level of |
'| each pixel within the image. Therefore, each character in the string (a byte) will need 8 pixels to |
'| store the equivalent binary value. A binary value of 1 will set a pixel's alpha level to 255 and a |
'| binary value of 0 will set a pixel's alpha level to 254. This very slight variation in alpha levels |
'| will be imperceivable to the naked eye when viewing the image. |
'| A header and footer of 3 bytes is added to beginning and end of the string data. The header is used |
'| to identify that encoded information is contained within the image. The footer identifies the end of |
'| the string data contained within the image. |
'| |
'| The size of an image will determine the maximum size of the encoded string it can hold. |
'| |
'| String_Size = (Image_Width x Image_Height \ 8) - 6 |
'\_______________________________________________________________________________________________________/
DIM m AS _MEM ' memory block of image
DIM em AS _MEM ' memory block of image to encode
DIM ei AS LONG ' copy of image to encode
DIM s AS STRING ' string to encode within image
DIM p AS LONG ' position within string
DIM o AS _OFFSET ' pixel offset within memory block
DIM c AS _UNSIGNED _BYTE ' single character value within string
DIM a AS _UNSIGNED _BYTE ' pixel alpha value to write
DIM b AS _BYTE ' bit counter
DIM b(7) AS _UNSIGNED _BYTE ' place value of bit counter
DIM HeadFoot AS STRING * 3 ' string header and footer
'+-------------------------------------------------------------------------------------------------------+
'| Check for a valid image before proceeding. |
'| ========================================== |
'+------------------------------+ |
EncodeImage& = -1 ' | assume this image is invalid |
IF i < -1 THEN ' | is this a valid image handle? |
IF _PIXELSIZE(i) = 4 THEN ' | is this a 32 bit color image? |
' +------------------------------------------------------------------------+
'+-----------------------------------------------------------------------------------------------+
'| Store binary place holder values |
'| ================================ |
'+-----------+ |
b(0) = 1 ' | define 8 bit binary place values |
b(1) = 2 ' | |
b(2) = 4 ' | |
b(3) = 8 ' | |
b(4) = 16 ' | |
b(5) = 32 ' | |
b(6) = 64 ' | |
b(7) = 128 ' | |
' +-----------------------------------------------------------------------------------+
'+-----------------------------------------------------------------------------------------------+
'| Add an identifying header and footer to string Note: The possibility of this header/ |
'| ============================================== footer combination found in a |
'+-------------------------------+ string will be very low but not |
HeadFoot = "U" + CHR$(0) + "U" ' | 010101010000000001010101 a zero chance. May want to scan |
s = HeadFoot + st + HeadFoot ' | add header and footer before proceeding. ( use INSTR ) |
' +---------------------------------------------------------------+
'+-----------------------------------------------------------------------------------------------+
'| Use image memory manipulation for speed |
'| ======================================= |
'+-----------------+ |
m = _MEMIMAGE(i) ' | create image memory block |
' +-----------------------------------------------------------------------------+
'+-----------------------------------------------------------------------------------------------+
'| Ensure that the string will fit inside the image |
'| ================================================ |
'+-----------------------------+ |
IF LEN(s) > m.SIZE \ 32 THEN ' | will string fit into image? |
_MEMFREE m ' | no, free image memory block |
s = "" ' | clear variable |
ELSE ' | Yes, the string will fit |
' +-----------------------------------------------------------------+
'+-------------------------------------------------------------------------------------------+
'| Turn error checking off for speed |
'+-------------------------------------------------------------------------------------------+
$CHECKING:OFF
'+-------------------------------------------------------------------------------------------+
'| Use image memory manipulation for speed |
'| ======================================= |
'+-------------------+ |
ei = _COPYIMAGE(i) ' | create image to encode |
em = _MEMIMAGE(ei) ' | create image to encode memory block |
' +-----------------------------------------------------------------------+
'+-------------------------------------------------------------------------------------------+
'| Endcode string into image |
'| ========================= |
'+------------------------------------------------+ |
p = 1 ' | start at text position 1 |
o = 0 ' | reset pixel offset location |
DO ' | begin encoding loop |
c = ASC(s, p) ' | get next character value in string |
b = 0 ' | reset bit counter |
DO ' | begin bit identification loop |
IF c AND b(b) THEN a = 255 ELSE a = 254 ' | 255 = 1, 254 = 0 |
_MEMPUT em, em.OFFSET + o + 3, a ' | adjust alpha value in encoded image |
o = o + 4 ' | next pixel position in memory block |
b = b + 1 ' | next bit in character |
LOOP UNTIL b = 8 ' | leave when all 8 bits processed |
p = p + 1 ' | next character in string |
LOOP UNTIL p > LEN(s) ' | leave when string exhausted |
_MEMFREE m ' | free image memory block |
_MEMFREE em ' | free encoded image memory block |
' +------------------------------------------+
'+-------------------------------------------------------------------------------------------+
'| Turn error checking back on |
'+-------------------------------------------------------------------------------------------+
$CHECKING:ON
'+-------------------------------------------------------------------------------------------+
'| Return handle of encoded image |
'| ============================== |
'+------------------+ |
EncodeImage& = ei ' | return handle of encoded image |
' +------------------------------------------------------------------------+
END IF
END IF
END IF
END FUNCTION
' ______________________________________________________________________________________________________
'/ \
FUNCTION DecodeImage$ (i AS LONG) ' DecodeImage$ |
' __________________________________________________________________________________________________|____
'/ \
'| Extracts a coded string from within an image. |
'| |
'| i - image to decode |
'| |
'| Returns: string extracted from image |
'| null if no coded string was found within image or an error occurred |
'| |
'| Note: Only works with 32 bit images |
'\_______________________________________________________________________________________________________/
DIM m AS _MEM ' memory block of image
DIM e AS _OFFSET ' end of memory block
DIM s AS STRING ' string containing decoded data
DIM c AS _UNSIGNED _BYTE ' single character value
DIM o AS _OFFSET ' pixel offset within memory block
DIM a AS _UNSIGNED _BYTE ' pixel alpha value
DIM b AS _BYTE ' bit counter
DIM b(7) AS _UNSIGNED _BYTE ' place value of bit counter
DIM HeadFoot AS STRING * 3 ' string header and footer
DIM Done AS _BYTE ' finished processing flag
'+-------------------------------------------------------------------------------------------------------+
'| Check for a valid image before proceeding. |
'| ========================================== |
'+------------------------------+ |
s = "" ' | assume this image is invalid |
IF i < -1 THEN ' | is this a valid image handle? |
IF _PIXELSIZE(i) = 4 THEN ' | is this a 32 bit color image? |
' +------------------------------------------------------------------------+
'+-----------------------------------------------------------------------------------------------+
'| Store binary place holder values |
'| ================================ |
'+-----------+ |
b(0) = 1 ' | define 8 bit binary place values |
b(1) = 2 ' | |
b(2) = 4 ' | |
b(3) = 8 ' | |
b(4) = 16 ' | |
b(5) = 32 ' | |
b(6) = 64 ' | |
b(7) = 128 ' | |
' +-----------------------------------------------------------------------------------+
'+-----------------------------------------------------------------------------------------------+
'| Use image memory manipulation for speed |
'| ======================================= |
'+----------------------+ |
m = _MEMIMAGE(i) ' | create image memory block |
e = m.OFFSET + m.SIZE ' | end of memory block |
' +------------------------------------------------------------------------+
'+-----------------------------------------------------------------------------------------------+
'| Turn error checking off for speed |
'+-----------------------------------------------------------------------------------------------+
$CHECKING:OFF
'+-----------------------------------------------------------------------------------------------+
'| Extract string from image |
'| ========================= |
'+---------------------------------------+ |
HeadFoot = "U" + CHR$(0) + "U" ' | 010101010000000001010101 string header and footer |
Done = 0 ' | reset finished flag |
s = "" ' | reset string |
o = 0 ' | reset pixel offset |
DO ' | cycle through image pixels |
b = 0 ' | reset bit count |
c = 0 ' | reset character value |
DO ' | cycle through character bits |
_MEMGET m, m.OFFSET + o + 3, a ' | get alpha value of pixel |
IF a = 255 THEN c = c + b(b) ' | if 1 then add place value |
b = b + 1 ' | increment bit counter |
o = o + 4 ' | increment to next image pixel |
LOOP UNTIL b = 8 ' | leave when 8 bits examined |
s = s + CHR$(c) ' | add character to string |
'+-----------------------------------+ |
'| Search for string header |
'| ======================== |
'+------------------------------------+ |
IF o = 96 THEN ' | first 3 characters extracted |
IF s <> HeadFoot THEN Done = -1 ' | report encoded string not found if no header |
s = "" ' | remove header (null return for no header found) |
ELSE ' | beyond first 3 characters |
'+--------------------------------+ |
'| Search for string footer |
'| ======================== |
'+--------------------------------+ |
IF RIGHT$(s, 3) = HeadFoot THEN ' | check for footer (end of string) |
Done = -1 ' | report encoded string found |
s = LEFT$(s, LEN(s) - 3) ' | remove footer |
END IF ' | |
END IF ' | |
LOOP UNTIL (o = e) OR Done ' | leave at end of memory block or done processing |
_MEMFREE m ' | free memory block |
' +------------------------------------------------------+
'+-----------------------------------------------------------------------------------------------+
'| Set string to null if a footer was not found (image corruption perhaps?) |
'| ============================================ |
'+------------------------+ |
IF Done = 0 THEN s = "" ' | a footer was not found |
' +----------------------------------------------------------------------+
'+-----------------------------------------------------------------------------------------------+
'| Turn error checking back on |
'+-----------------------------------------------------------------------------------------------+
$CHECKING:ON
END IF
END IF
'+-------------------------------------------------------------------------------------------------------+
'| Return string found |
'| =================== |
'+-----------------+ |
DecodeImage$ = s ' | return string |
' +-------------------------------------------------------------------------------------+
END FUNCTION
' ______________________________________________________________________________________________________
'/ \
FUNCTION EncodedStringExists% (i AS LONG) ' EncodedStringExists% |
' __________________________________________________________________________________________________|____
'/ \
'| Tests an image for an encoded string. |
'| |
'| i - image to test for encoded string |
'| |
'| Returns: 0 if no encoded string found |
'| -1 if an encoded string was found |
'\_______________________________________________________________________________________________________/
DIM m AS _MEM ' memory block of image
DIM e AS _OFFSET ' end of memory block
DIM s AS STRING ' string containing decoded data
DIM c AS _UNSIGNED _BYTE ' single character value
DIM o AS _OFFSET ' pixel offset within memory block
DIM a AS _UNSIGNED _BYTE ' pixel alpha value
DIM b AS _BYTE ' bit counter
DIM b(7) AS _UNSIGNED _BYTE ' place value of bit counter
DIM HeadFoot AS STRING * 3 ' string header and footer
'+-------------------------------------------------------------------------------------------------------+
'| Check for a valid image before proceeding. |
'| ========================================== |
'+------------------------------+ |
EncodedStringExists% = 0 ' | assume this image is invalid |
IF i < -1 THEN ' | is this a valid image handle? |
IF _PIXELSIZE(i) = 4 THEN ' | is this a 32 bit color image? |
' +------------------------------------------------------------------------+
'+-----------------------------------------------------------------------------------------------+
'| Turn error checking off for speed |
'+-----------------------------------------------------------------------------------------------+
$CHECKING:OFF
'+-----------------------------------------------------------------------------------------------+
'| Store binary place holder values |
'| ================================ |
'+-----------+ |
b(0) = 1 ' | define 8 bit binary place values |
b(1) = 2 ' | |
b(2) = 4 ' | |
b(3) = 8 ' | |
b(4) = 16 ' | |
b(5) = 32 ' | |
b(6) = 64 ' | |
b(7) = 128 ' | |
' +-----------------------------------------------------------------------------------+
'+-----------------------------------------------------------------------------------------------+
'| Use image memory manipulation for speed |
'| ======================================= |
'+----------------------+ |
m = _MEMIMAGE(i) ' | create image memory block |
e = m.OFFSET + m.SIZE ' | end of memory block |
' +------------------------------------------------------------------------+
'+-----------------------------------------------------------------------------------------------+
'| Extract header from image |
'| ========================= |
'+---------------------------------------+ |
HeadFoot = "U" + CHR$(0) + "U" ' | 010101010000000001010101 string header and footer |
s = "" ' | reset string |
o = 0 ' | reset pixel offset |
DO ' | cycle through image pixels |
b = 0 ' | reset bit count |
c = 0 ' | reset character value |
DO ' | cycle through character bits |
_MEMGET m, m.OFFSET + o + 3, a ' | get alpha value of pixel |
IF a = 255 THEN c = c + b(b) ' | if 1 then add place value |
b = b + 1 ' | increment bit counter |
o = o + 4 ' | increment to next image pixel |
LOOP UNTIL b = 8 ' | leave when 8 bits examined |
s = s + CHR$(c) ' | add character to string |
LOOP UNTIL (o = e) OR (o = 96) ' | leave at end of memory block or 3 characters retrieved|
_MEMFREE m ' | free memory block |
' +-------------------------------------------------------+
'+-----------------------------------------------------------------------------------------------+
'| Turn error checking back on |
'+-----------------------------------------------------------------------------------------------+
$CHECKING:ON
'+-----------------------------------------------------------------------------------------------+
'| Return status of string within image |
'| ==================================== |
'+-----------------------------------------------+ |
IF s = HeadFoot THEN EncodedStringExists% = -1 ' | return -1 if header found |
' +-----------------------------------------------+
END IF
END IF
END FUNCTION
RE: Coded Images - a740g - 02-23-2024
Nice. I'll try this when I get off work.
So, this is like a watermark in the image? From a quick glance at the code, it seems it is getting the info from the alpha channel.
RE: Coded Images - TerryRitchie - 02-23-2024
(02-23-2024, 05:31 PM)a740g Wrote: Nice. I'll try this when I get off work.
So, this is like a watermark in the image? From a quick glance at the code, it seems it is getting the info from the alpha channel. Yes, you could use it to watermark an image. The alpha channel of each pixel is slightly altered to hold the string data. A pixel's alpha value of 255 equates to a 1 and a value of 254 equates to a 0. To the naked eye the slight change is imperceptible.
It's best to use images, such as photographs, that are opaque (all alpha values are 255). You wouldn't want to use these routines on images where the alpha channel is important, such as sprites or sprite sheets that contain transparency, unless the code you write to use these routines takes this into account.
RE: Coded Images - SMcNeill - 02-23-2024
I used to use this exact method on a lot of photos to embed time/date/0eople/place info. But then I ran into the problem that nobody else could/would ever know how to decode them. Simple solution I came up with?
Just APPEND the text data and size to the end of the image. Then I can read those last 4 bytes to get size, back up LOF - Size and GET text$ of size to read it back into a program.
And best of all?
ANYONE can simply open that image in any text/hex editor and scroll to the end and easily retrieve that info.
It's a much quicker and usable method to embed text onto images.
RE: Coded Images - SMcNeill - 02-23-2024
A quick and simple example of this:
Code: (Select All)
CHDIR "z:"
photo$ = "steve.png"
'AppendData photo$, "This is Steve, back before he graduated from high school."
text$ = GetData(photo$)
SCREEN _NEWIMAGE(480, 640, 32)
image = _LOADIMAGE(photo$)
_PUTIMAGE (0, 0)-(480, 600), image
_PRINTSTRING (0, 610), text$
SLEEP
SUB AppendData (image$, text$)
DIM AS LONG f, l
f = FREEFILE
PRINT image$, _FILEEXISTS(image$)
OPEN image$ FOR BINARY AS #f
PUT #f, LOF(f) + 1, text$
l = LEN(text$)
PUT #f, , l
CLOSE #f
END SUB
FUNCTION GetData$ (image$)
DIM AS LONG l, f, l2
f = FREEFILE
OPEN image$ FOR BINARY AS f
l = LOF(f)
GET #f, l - 3, l2
text$ = SPACE$(l2)
GET #f, l - 3 - l2, text$
CLOSE #f
GetData$ = text$
END FUNCTION
Note that I'm commented out the line which appends the data to the image in the code above. This is because I've already appended it to the image here, so all I have to do is GetData back from the image, to showcase it working for us.
And if you open that file up in any text editor, you'll see a bunch of jibberish like the following:
Quote:...stuff before this...
ӁϾ _ ` ݻ> 7 s = B~ o _ 5A @Ϩ ' N>ȧ 7 :{ ! X ? !م 1b3:^ џ
3 Q&D; "< h ^ - r /}i= ?hC Ա @ɞ ~<l\ K..c | K 4 Y
i0h W\ ܹ } Mwv U 7 p > 9mC0 + ={ L^ '& #cN8C g ֭ f, c lx[; a h $: ti-> } *]
7 8 ӷ ߄yy֬' f _'9c}` ﮷ o t K 3 oyq ֤vmx 2Q Y _r v i 1; :
H ڳļ }Reҙ `ͭ }+ \' |W = y f{6e S pn㫭 : { ʵ [r
oő) ̇ ^e ? ߧm rS `\ Kd G<
>u X x [ o-' ^ Ե ᜕ ^%` l4 @ 4 Ar` r% tV Ӯ K2G W 뿴 K G Ŵ ; g J;H Y0 f 8 o '> v ٸ d h ۉ 1 䛿
. W=9X3 R Wd A c
H\x [ { 0 C< W~ $z m u ~ K_ , gz
i w` S+ " q n (^ 5 3 w 7WG6 J| ?8 wOo y ׃ 3o?xp w s Og ǿu{v y ־Ÿ}{~q dۛ ~
˕ cr \ - | ux y Zw z־ d K 6] \ n 2 k 9 { z A = X > p 8 >_ %ѦC[ \u d z / `^ ը- | : :>yv + RrO eXIfMM * S IEND B` This is Steve, back before he graduated from high school.9
I deleted a bunch of this image data as it's irrelevant to any text editor. The main thing here to pay attention to is what's at the END of that file... Plain text, which can be easily read by anyone who wants to open it up and look at it. No decoding or ninja skills required.
RE: Coded Images - Dimster - 02-23-2024
Lord you are a hansom man McNeill.
RE: Coded Images - SMcNeill - 02-23-2024
(02-23-2024, 07:56 PM)Dimster Wrote: Lord you are a hansom man McNeill.
Gosh! I'm flattered @Dimster. Unfortunately, I just don't think you're my type.
RE: Coded Images - Dimster - 02-23-2024
Ya, it was an observation not a pass. Is the photo a younger you?
RE: Coded Images - NakedApe - 02-24-2024
Dimster, from the gibberish in Steve's post above: "Steve, back before he graduated from high school.9"
I love the stache.
RE: Coded Images - Dimster - 02-24-2024
Geeeeze, thanks for pointing that out Mr. NakedApe. I have found as I age I have tenancy to read for the jest of things rather than reading right to the end. Bad habit for a coder.
|