Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- """A 24-bit bitmap uses 8 bits to represent each of the three color values (red, green, and blue) of each pixel.
- The red alone has 256 different levels of red intensity.
- The difference between 11111111 and 11111110 in the value for red intensity is likely to be undetectable by the human eye.
- Therefore, the least significant bit can be used more or less undetectably for something else other than color information.
- If that is repeated for the green and the blue elements of each pixel as well,
- it is possible to encode one letter of ASCII text for every three pixels
- or .. each colour channel can carry a different message."""
- from PIL import Image # Import the image module from PIL
- #
- #------------
- def encStegan( srcImg = None, seq = "", colourChannel = 0 ):
- """Morph image file with hidden bits .. eg. ASCII secret message !
- RETURN new version of Image ( or an error String )"""
- if ( not srcImg or not seq ): return "Function REQUIRES an image and some text."
- if ( colourChannel not in range(3)): return "Colour channel must be in (0,1,2) for R, G or B"
- srcImg = srcImg.convert('RGB') # Convert the image to RGB
- srcSize = srcImg.size
- width, height = srcSize # (unpack from tuple) the image's width and height
- print('Image SIZE:', width, '*', height, '=', width * height, 'pixels\n' )
- imgSeqLimit = (( width * height ) // 8 ) - 1 # less 1 to allow for \x0
- if ( len( seq ) > imgSeqLimit ): return "NOT enough pixels to encode. Limit is "+ imgSeqLimit
- #-----------------
- newImg = Image.new('RGB', srcSize ) # create a grid for the output image
- if ( isinstance( seq, str )) : # is seq a String ?
- intsList = [ ord(c) for c in seq ] # map String to list of integer ordinals (codepoints)
- else:
- intsList = list( seq ) # else .. assume already a <sequence of integers> eg. tuple/bytes etc.
- #
- #--------------
- intsList.append(0x0) # add NULL on end
- # build binary string ..
- binary_message = ""
- for i in intsList:
- bin_as_string = '{0:08b}'.format( i )
- binary_message += bin_as_string
- # --------------
- lenBits = len( binary_message )
- bitIndex = 0
- copyPixel = False
- # nest loops to access each pixel within image ..
- for row in range( height ):
- for col in range( width ):
- #
- pixXY = (col, row)
- thisColour = srcImg.getpixel( pixXY ) # (r, g, b)
- if ( copyPixel ): # set True when txt is exhausted
- newImg.putpixel( pixXY, thisColour )
- else:
- # I am working with pixel (R,G or B byte) sequences
- # .. using bit0 from each byte to make a hidden bit sequence
- # Replace the low-order bit (bit0) in the 8-bit channelValue
- # ..to hold a bit value from the int being encoded
- channelValue = thisColour[ colourChannel ] # colourChannel 0,1,2 ==> r, g, b
- channelStr = '{0:b}'.format( channelValue )
- channelStr = channelStr[:-1] + binary_message[ bitIndex ] # replace LSB
- channelValue = int( channelStr, 2 ) # parse 'binary string' back to byte (int)
- newColor = list( thisColour )
- newColor[ colourChannel ] = channelValue # replace
- newImg.putpixel( pixXY, tuple( newColor ))
- # ========
- bitIndex += 1
- if ( bitIndex >= lenBits ): #
- copyPixel = True # FINISHED encoding text. Just copy pixels from now on !
- #
- return newImg
- #----------------
- #------------
- def decStegan( srcImg = None, colourChannel = 0):
- """Scan image file for hidden bytes .. eg. Unicode (ASCII) secret message !"""
- srcImg = srcImg.convert('RGB') # Convert the image to RGB
- width, height = srcImg.size # Assign the image's width and height to variables
- print('Size:', width, '*', height )
- pixelCount = 0 # wrap at 8
- output_bits = ""
- #output_text = ""
- byteList = []
- #
- for row in range( height ):
- for col in range( width ):
- #
- thisColour = srcImg.getpixel( (col, row) ) # (r, g, b)
- channelValue = thisColour[ colourChannel ] # colourChannel 0,1,2 ==> r, g, b
- #
- # I am working with 8 pixel (byte) sequences
- # .. taking bit0 from each of the 8 channelValue bytes to make a hidden byte
- channelStr = '{0:b}'.format( channelValue )
- output_bits += channelStr[ -1: ] # last char is LSB binary digit
- pixelCount += 1
- if ( pixelCount >= 8 ): # got to 8 ..
- #
- newByte = int( output_bits, 2 )
- #print( hex( newByte )) # DEBUG
- if ( newByte == 0 ): return byteList # 0x00 so return RESULT !
- # ====== ========
- # else NOT 0x00 NULL terminator so ..
- # ..add byte to sequence, then reset for next 8 pixel sequence
- byteList.append( newByte )
- # RESET for new byte ..
- pixelCount = 0
- output_bits = ""
- #
- #
- return [] # "NO NULL terminator"
- #------------------
- #-- ----------------
- def showIntSequence( ordSequence = [], assumeUTF8 = True ) :
- print( 'OUTPUT follows ..')
- print( '\nDECIMAL ..')
- print( ordSequence )
- print( '\nBINARY .. showing a minimum of 8 bits ..')
- resChars = [ '{0:08b}'.format(n) for n in ordSequence ]
- print( ",".join(resChars))
- print( '\nHEX ..')
- resChars = [ hex(n) for n in ordSequence ]
- print( ",".join(resChars))
- print( '\nASCII ..')
- resChars = [ ascii(chr(n)) for n in ordSequence ]
- print( ",".join(resChars))
- print( '\nUnicode TEXT from codepoints .. show with non-printables excluded: < x20 | == x7F .. IF ANY')
- print( '------------')
- resChars = []
- for thisOrd in ordSequence :
- if ( thisOrd >= 0x20 and thisOrd != 0x7F ) : resChars.append( chr( thisOrd ))
- #
- print( "".join(resChars)) # make a String of it
- if ( assumeUTF8 ):
- print('\nUTF-8 Unicode ..')
- print( '-------------')
- by = bytes( bytesFound )
- print(by.decode())
- #
- print("===============================================================")
- #
- #--------------
- # TEST CALLS ..
- startFile = "encoded_image.png"
- print('\nReading file:', startFile, ' ...')
- print( '************ -----------------')
- encImg = Image.open( startFile )
- #
- print('Scanning RED channel for Steganography text .. DECODING in RED 8-bit using bin digit chars !!')
- print('====================')
- bytesFound = decStegan( encImg, 0 )
- showIntSequence( bytesFound )
- print('Scanning GREEN channel for Steganography text .. DECODING in GREEN 8-bit using bin digit chars !!')
- print('======================')
- bytesFound = decStegan( encImg, 1 )
- showIntSequence( bytesFound )
- #-----------------------------------------------------
- inSeq = "\u262f\u262e" # '☯☮' #
- print('Encoding', inSeq, 'in BLUE 8-bit UTF-8 ... please wait ...')
- encImg = encStegan( encImg, inSeq.encode(), 2 ) # 2 -> BLUE **!
- print('DECODING in BLUE 8-bit')
- bytesFound = decStegan( encImg, 2 )
- showIntSequence( bytesFound )
Add Comment
Please, Sign In to add comment