Advertisement
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 blue alone has 256 different levels of blue intensity.
- The difference between 11111111 and 11111110 in the value for blue 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 red 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 getMSB_bitmask( numBits = 8 ):
- """ eg. (numBits == 8) => 0x80 == 0b10000000 """
- if (numBits < 1 ): numBits = 1 # guard
- mask = 0x01
- mask <<= ( numBits - 1 )
- return mask
- #
- #------------
- def encStegan( srcImg = None, seq = "", colourChannel = 0, numBits = 8 ):
- """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
- imgSeqLimit = (( width * height ) // numBits ) - 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 )) :
- 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>
- #
- intsList.append(0x00) # add NULL on end
- lenInts = len( intsList )
- MSB_mask = getMSB_bitmask( numBits ) # 8 bits in a byte => 0x80 == 0b10000000
- ########
- intsIndex = 0
- copyPixel = False
- bitPattern = intsList[0] # get the first int (binary sequence) to encode
- bitMask = MSB_mask # alg shifts this single bit >> to test against a bit pattern
- # nest loops to modify 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:
- channelValue = thisColour[ colourChannel ] # colourChannel 0,1,2 ==> r, g, b
- #
- # I am working with pixel (R,G or B byte) sequences
- # .. using bit0 from each byte to make a hidden bit sequence
- # tweak the low-order bit (bit0) in the 8-bit channelValue
- # ..to hold a bit value from the int being encoded
- if ( bitPattern & bitMask ): # test if bit set to 1 ?
- channelValue |= 0x01 # set bit0 to 1 in colour channel
- else:
- channelValue &= 0xFE # set bit0 to 0 in colour channel .. 0xFE == 0b11111110
- #
- newColor = list( thisColour )
- newColor[ colourChannel ] = channelValue # replace
- newImg.putpixel( pixXY, tuple( newColor ))
- # ========
- bitMask >>= 1 # work down through bits in the int (byte) being encoded
- if ( not bitMask ): # bitMask now 0x00 -> all bits mapped for bitPattern
- #
- intsIndex += 1
- if ( intsIndex >= lenInts ):
- copyPixel = True # FINISHED encoding text. Just copy pixels from now on !
- else:
- bitPattern = intsList[ intsIndex ] # get next int to encode
- bitMask = MSB_mask
- #
- return newImg
- #----------------
- #------------
- def decStegan( srcImg = None, colourChannel = 0, numBits = 8 ):
- """Scan image file for hidden bytes .. eg. 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
- MSB_mask = getMSB_bitmask( numBits ) # 8 bits in a byte => 0x80 == 0b10000000
- ########
- intsList = [] # results ..
- bitMask = MSB_mask # alg shifts this single bit >> to set bit in corresponding position
- buildBits = 0x0 # .. set using binary OR | with bitMask
- 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
- #print( channelValue, thisColour, hex(buildBits)) # DEBUG .. use to comprehend ..!
- # if bit0 is set on (1) in image data, set bit in bitMask position
- if ( channelValue & 0x01 ): buildBits |= bitMask # .. set bit using binary OR |
- bitMask >>= 1 # work down through bits in the int
- if ( not bitMask ): # all bits captured for this value ..
- #
- #print( hex( buildBits )) # DEBUG
- #
- if ( not buildBits ): return intsList # 0x0 NULL terminator -> so return RESULT !
- # ====== ========
- # else NOT 0x0 NULL terminator so ..
- # ..add int to list, then reset for next pixel sequence
- intsList.append( buildBits )
- # RESET for new bits ..
- bitMask = MSB_mask
- buildBits = 0x0
- #
- return [] # "NO NULL terminator"
- #------------------
- #------------
- def decStegan_8bit( srcImg = None, colourChannel = 0):
- """Scan image file for hidden bytes .. eg. ASCII secret message !"""
- """ NOTE: this function is RETIRED .. superseded by decStegan() """
- """ ... it works fine for 8-bit. It uses << """
- srcImg = srcImg.convert('RGB') # Convert the image to RGB
- width, height = srcImg.size # Assign the image's width and height to variables
- buildByte = 0x00 # .. using | ( binary OR ) and << (left_shift) 1
- byteList = []
- pixelCount = 0 # wrap at 8
- 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
- buildByte <<= 1 # left shift puts a 0 at bit0 .. move ALL bits left, ready for next bit
- # if bit0 is set on (1) in image data, set (copy to) bit0 in buildByte
- if ( channelValue & 0x01 ): buildByte |= 0x01 # AND (&) tests .. OR (|) sets bit(s) to 1
- # print( channelValue, thisColour, hex(buildByte)) # DEBUG .. use to comprehend ..!
- pixelCount += 1
- if ( pixelCount >= 8 ): # got to 8 ..
- #
- #print( hex( buildByte )) # DEBUG
- #
- if ( not buildByte ): 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( buildByte )
- # RESET for new byte ..
- pixelCount = 0
- buildByte = 0x00 # 0
- #
- #
- #
- return [] # "NO NULL terminator"
- #------------------
- #-- ----------------
- def showByteSequence( ordSequence ) :
- print( 'OUTPUT follows ..')
- print( 'DECIMAL ..')
- print( ordSequence )
- print( 'BINARY ..')
- resChars = [ bin(n) for n in ordSequence ]
- print( ",".join(resChars))
- print( 'HEX ..')
- resChars = [ hex(n) for n in ordSequence ]
- print( ",".join(resChars))
- print( 'ASCII ..')
- resChars = [ ascii(chr(n)) for n in ordSequence ]
- print( ",".join(resChars))
- print( 'TEXT .. show only standard ASCII with non-printables excluded .. IF ANY')
- resChars = []
- for thisOrd in ordSequence :
- if ( thisOrd >= 32 and thisOrd < 127 ) : resChars.append( chr( thisOrd ))
- #
- print( "".join(resChars)) # make a String of it
- print("--------------------------------------------------------------------")
- #
- #--------------
- # TEST CALLS ..
- print('\nReading file "encoded_image.png" ...')
- encImg = Image.open('encoded_image.png')
- #
- print('Scanning RED channel for Steganography text ..')
- print('====================')
- bytesFound = decStegan( encImg, 0 ) # 0 -> red
- showByteSequence( bytesFound )
- print('Scanning GREEN channel for Steganography text ..')
- print('======================')
- bytesFound = decStegan( encImg, 1 ) # 1 -> green
- showByteSequence( bytesFound )
- print('Scanning BLUE channel for Steganography text ..')
- print('=====================')
- bytesFound = decStegan( encImg, 2 ) # 2 -> blue
- showByteSequence( bytesFound )
- #---------------
- inSeq = (100, 101, 102, 103)
- print('Encoding', inSeq, 'in RED 7-bit')
- encBINimg = encStegan( encImg , inSeq, 0, 7 ) # 0 -> RED 7-bit **!
- bytesFound = decStegan( encBINimg, 0, 7 )
- showByteSequence( bytesFound )
- inSeq = (200, 201, 202, 203)
- print('Encoding', inSeq, 'in GREEN 8-bit')
- encBINimg = encStegan( encBINimg, inSeq, 1, 8 ) # 1 -> GREEN 8-bit **!
- bytesFound = decStegan( encBINimg, 1, 8 )
- showByteSequence( bytesFound )
- inSeq = [511,510,509,508,507]
- print('Encoding', inSeq, 'in BLUE 9-bit')
- encBINimg = encStegan( encBINimg, inSeq, 2, 9 ) # 2 -> BLUE 9-bit **!
- bytesFound = decStegan( encBINimg, 2, 9 )
- showByteSequence( bytesFound )
- encBINimg.save("encoded_image_R7_G8_B9.png")
- # ** nb. can write/read 16-bit etc. **!
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement