#import os
#os.chdir("chemin du répertoire images")

from PIL import Image
import numpy as np

##Image secrète
#Méthode de déchiffrement
from operator import xor

"""
Question 1 :
"""
def dechiffrement(nom1,nom2):
    #Ouverture des fichiers images
    image1 = Image.open(nom1)
    image2 = Image.open(nom2)
    #Transformations en tableaux numpy
    tab1 = np.array(image1)
    tab2 = np.array(image2)
    #Dimensions des images
    ligne,colonne,profondeur = np.shape(image1)
    ligne2,colonne2,profondeur2 = np.shape(image2)
    #On s'assure que els images ont la même dimension
    assert (ligne,colonne,profondeur) == (ligne2,colonne2,profondeur2)
    #Création du tableau numpy résultat
    tab = np.zeros((ligne,colonne),dtype=np.uint8)
    #On parcourt les images pixel par pixel
    for i in range(ligne) :
        for j in range(colonne) :
            R1,V1,B1 = tab1[i][j] #Intensités de couleurs du pixel (i,j) de l'image 1
            R2,V2,B2 = tab2[i][j] #Intensités de couleurs du pixel (i,j) de l'image 2
            if R1==R2: #Si les intensités de rouge sont égales
                tab[i][j]=xor(V1,V2) #On garde le OU exclusif des intensités de vert
            else: #Sinon
                tab[i][j]=xor(B1,B2) #On garde le OU exclusif des intensités de bleu
    return Image.fromarray(tab) #On renvoie l'image correspondant au tableau tab

lieu = dechiffrement("image_secrete_1.png","image_secrete_2.png")
lieu.show() #Affichage de l'image
"""
Le lieu de rendez-vous est la Tour de Pise en Italie.
"""



##Stéganographie ou l'art de cacher un message dans une image
#Décoder un message
"""
Question 2 :
"""
def decoderOctet(tab,pos):
    res = 0 #Entier codé
    #On parcourt les 8 octets à décoder
    for i in range(8):
        res = res*2 + tab[pos+i]%2 #On augmente res du bit suivant dans sa décomposition binaire
    return res

T=[[58,32,152,128,36,200,9,147],
    [54,175,199,32,133,53,78,241],
    [190,31,71,80,179,81,71,129],
    [102,27,75,247,116,93,250,86]]
T=np.array(T) #Transformation de la liste en tableau numpy

ligneT,colonneT = T.shape #Dimension du tableau T
T.shape = (1,ligneT*colonneT)
T = T[0,:] #T est maintenant constitué d'une seule ligne

print(decoderOctet(T,0))

"""
Question 3 :
"""
def decoderMessage(image):
    tab = np.array(image) #Tableau numpy correspondant à l'image
    ligne,colonne = tab.shape #Dimension du tableau (image en noir et blanc)
    #Transformation du tableau en ligne
    tab.shape = (1,ligne*colonne)
    tab = tab[0,:]
    taille = decoderOctet(tab,0) #Taille du message à décoder, codée sur les 8 premiers octets
    mess = "" #Message décodé
    pos = 8 #Premier octet codant le prochain caractère du message
    #Tant qu'on a pas fini de décoder le message
    while taille>0 :
        n = decoderOctet(tab,pos) #On décode l'entier suivant
        mess += chr(n) #On ajoute le carctère correspondant au message
        pos += 8 #On actualise la position de recherche du prochain entier codé
        taille -= 1 #On a décodé un caractère de plus
    return mess #On renvoie le message décodé

imageFete = Image.open("fete.png")
print(decoderMessage(imageFete))

#Coder un message
"""
Question 4 :
"""
def valBit(pos,n):
    #Si on veut le bit de poids faible
    if pos==0 :
        return n%2 #Il s'agit de la parité de n
    else :
        return valBit(pos-1,(n-n%2)//2) #Sinon, on cherche le bit d'indice pos-1 dans l'entier (n-a0)/2 où a0=n%2

print(valBit(5,33))
print(valBit(0,3))

"""
Question 5 :
"""
def coderOctet(T,pos,octet):
    #On parcourt les 8 positions codant l'entier
    for i in range(8) :
        bit = valBit(7-i,octet) #bit de l'entier octet en position 7-i
        #Si le bit de poids faible de l'entier d'indice pos n'est pas le même que le bit d'indice 7-i de l'entier octet, on modifie ce bit
        if not bit==T[pos+i]%2 and bit==0:
            T[pos+i] -= 1
        elif not bit==T[pos+i]%2 :
            T[pos+i] += 1
    return T

T0=[[59,33,153,128,36,200,9,147],
    [55,175,198,32,132,53,79,240],
    [190,30,71,81,178,81,71,129],
    [103,26,75,247,117,92,250,86]]
T0=np.array(T0)

ligneT0,colonneT0 = T0.shape
T0.shape = (1,ligneT0*colonneT0)
T0 = T0[0,:]

print(coderOctet(T0,0,3))

"""
Question 6 :
"""
def cacherMessage(image,mess) :
    tab = np.array(image) #Transformation de l'image en tableau numpy
    ligne,colonne = tab.shape #Dimension du tableau
    #Transformation du tableau en ligne
    tab.shape = (1,ligne*colonne)
    tab = tab[0,:]
    taille = len(mess) #Taille du message à cacher
    tab = coderOctet(tab,0,taille) #Encodage de la taille sur les 8 premiers octets
    pos = 8 #Première octet codant le prochain caractère
    ind = 0 #Indice du prochain caractère à coder
    #Tant qu'on n'a pas fini de coder le message
    while taille>0 :
        tab = coderOctet(tab,pos,ord(mess[ind])) #On modifie le tableau en encodant le caratère d'indice ind sur les 8 octets en partant de celui en position pos
        ind += 1 #On passe au caractère suivant
        pos += 8 #Le prochain caractère serait encoder à partir de l'octet pos+8
        taille -= 1 #On a un caractère de moins à coder
    tab.shape = (ligne,colonne) #On remet le tableau dans ses dimensions d'origine
    return Image.fromarray(tab) #On renvoie l'image associé au tableau

message = "Merci pour le carambar, il était vraiment délicieux. S'il faut finir, j'en veux bien un deuxième!"
imageCodee = cacherMessage(imageFete,message)
imageCodee.show()
print(decoderMessage(imageCodee))



##Stéganographie ou l'art de cacher des images dans des images
"""
Tests des opérateurs binaires.
"""
print(0b0001|0b0100) #Renvoie 0b0101 i.e. 5
print(0b1100|0b0011) #Renvoie 0b1111 i.e. 15
print(0b1101|0b1100) #Renvoie 0b1101 i.e. 13

print(0b0001&0b0111) #Renvoie 0b0001 i.e. 1
print(0b1100&0b0110) #Renvoie 0b0100 i.e. 4
print(0b1000&0b1110) #Renvoie 0b1000 i.e. 8

print(0b0110>>1) #Renvoie 0b0011 i.e. 3
print(0b0111>>2) #Renvoie 0b0001 i.e. 1
print(0b1000>>4) #Renvoie 0b0000 i.e. 0

print(0b0110<<1) #Renvoie 0b1100 i.e. 12
print(0b1110<<2) #Renvoie 0b1000 i.e. 8
#Python renvoie Ob111000 i.e. 56. Il ne se limite pas à 8 bits.
print(0b1001<<4) #Renvoie 0b0000 i.e. 0
#Python renvoie Ob10010000 i.e.144. Il ne se limite pas à 8bits.
#Dans les tableaux numpy que nous manipulons, les entiers sont forcèment écrits sur 8 bits, le décalage provoquera une troncature comme expliqué dans le sujet du TP.

#Extraire une image cachée
"""
Question 7 :
L'entier constitué des 4 bits de poids faible de n et de 4 0 correspond au décalage à gauche de n de 4 bits si les bits sortent!

L'entier constitué des 4 bits de poids fort de n et de 4 0 correspond à un décalage à droite de 4 bits puis un décalage à gauche de 4 bits.
"""

"""
Question 8 :
"""
def extraireImage(nom):
    image = Image.open(nom) #On ouvre le fichier au format image
    tab = np.array(image) #On crée le tableau numpy correspondant
    ligne,colonne,profondeur = tab.shape #Dimensions du tableau
    #On parcourt chaque pixel de l'image et chaque intensité de couleurs du pixel
    for i in range(ligne):
        for j in range(colonne):
            for k in range(profondeur):
                tab[i][j][k] = tab[i][j][k]<<4 #On ne garde que l'entier dont les 4 bits de poids forts sont les 4 bits de poids faible de l'image initiale
    return Image.fromarray(tab) #On renvoie l'image correspondant au tableau

imageCachee = extraireImage("crevetteDansBaleine.png")
imageCachee.show()

"""
Question 9 :
"""
def extraireImage2(nom):
    image = Image.open(nom) #On ouvre le fichier au format image
    tab = np.array(image) #On crée le tableau numpy correspondant
    ligne,colonne,profondeur = tab.shape #Dimensions du tableau
    #On parcourt chaque pixel de l'image et chaque intensité de couleurs du pixel
    for i in range(ligne):
        for j in range(colonne):
            for k in range(profondeur):
                tab[i][j][k] = tab[i][j][k]<<6 #On ne garde que l'entier dont les 2 bits de poids forts sont les 2 bits de poids faible de l'image initiale
    return Image.fromarray(tab) #On renvoie l'image correspondant au tableau

imageCachee2 = extraireImage2("asterixCache.png")
imageCachee2.show()
"""
Astérix et Obélix rencontrent des sangliers dans la forêt.
"""

#Incruster une image
"""
Question 10 :
On récupère les 4 bits de poids forts du deuxième entier comme précédemment : décalage à droite de 4 bits puis décalage à gauche de 4 bits. On a un premier entier n1.
On récupère les 4 bits de poids faible du troisième entier en effectuant un décalage à gauche de 4 bits puis un décalage à droite de 4 bits. On a un second entier n2.
On effctue n1 OU n2 et on obtient l'entier n voulu.
"""

"""
Question 11 :
"""
def cacherImage(nom1,nom2,p):
    #p = nombre de bits de poids forts du pixel à incruster à garder
    #On ouvre les fichiers au format image
    image1 = Image.open(nom1)
    image2 = Image.open(nom2) #Image à incruster
    #On crée les tableaux numpy correspondants
    tab1 = np.array(image1)
    tab2 = np.array(image2)
    #On récupère les dimensions des tableaux
    ligne1,colonne1,prof1 = tab1.shape
    ligne2,colonne2,prof2 = tab2.shape
    #On s'assure que l'image à incrustée est de taille inférieure à l'image initiale
    assert ligne2<=ligne1 and colonne2<=colonne1 and prof1==prof2
    #On parcourt l'image à incruster: chaque pixel et chaque intensité de couleurs du pixel
    for i in range(ligne2):
        for j in range(colonne2):
            for k in range(prof2):
                n1 = (tab1[i][j][k]>>p)<<p #On récupère les 8-p bits de poids fort de l'intensité de couleur du pixel de l'image à garder
                n2 = tab2[i][j][k]>>(8-p) #On récupère les p bits de poids fort de l'intensité de couleur du pixel de l'image à incruster
                tab1[i][j][k] = n1|n2 #On modifie l'image en effectuant le ou des entiers précédents
    return Image.fromarray(tab1) #On renvoie l'image correspondant au tableau

ImageCodee4 = cacherImage("baleineBleue.png","crevette.png",4)
ImageCodee4.show()

ImageCodee2 = cacherImage("baleineBleue.png","crevette.png",2)
ImageCodee2.show()

ImageCodee6 = cacherImage("baleineBleue.png","crevette.png",6)
ImageCodee6.show()



