302 lines
13 KiB
Python
302 lines
13 KiB
Python
import sys, os
|
|
from ui import Ui_Form
|
|
from PyQt5 import QtWidgets, QtCore, QtGui, QtSvg
|
|
from PIL import Image, ImageQt
|
|
|
|
class UiMain(QtWidgets.QMainWindow, Ui_Form):
|
|
|
|
def __init__(self, parent = None):
|
|
super(UiMain, self).__init__(parent)
|
|
self.setupUi(self)
|
|
self.initSlot()
|
|
self.show()
|
|
|
|
def initSlot(self):
|
|
self.pushButtonOpenCover.clicked.connect(self.EtOpenCover)
|
|
self.pushButtonClearCover.clicked.connect(self.EtCleanCover)
|
|
self.pushButtonSaveCover.clicked.connect(self.EtSaveCover)
|
|
|
|
self.pushButtonOpenSecret.clicked.connect(self.EtOpenSecret)
|
|
self.pushButtonClearSecret.clicked.connect(self.EtCleanSecret)
|
|
self.pushButtonSaveSecret.clicked.connect(self.EtSaveSecret)
|
|
|
|
self.pushButtonOpenContainer.clicked.connect(self.EtOpenContainer)
|
|
self.pushButtonClearContainer.clicked.connect(self.EtCleanContainer)
|
|
self.pushButtonSaveContainer.clicked.connect(self.EtSaveContainer)
|
|
|
|
self.pushButtonEncode.clicked.connect(self.EtEncode)
|
|
self.pushButtonDecode.clicked.connect(self.EtDecode)
|
|
|
|
def EtCleanCover(self):
|
|
self.labelCover.setPixmap(QtGui.QPixmap(""))
|
|
|
|
def EtCleanSecret(self):
|
|
self.labelSecret.setPixmap(QtGui.QPixmap(""))
|
|
|
|
def EtCleanContainer(self):
|
|
self.labelContainer.setPixmap(QtGui.QPixmap(""))
|
|
|
|
def EtOpenCover(self):
|
|
path, fileType = QtWidgets.QFileDialog.getOpenFileName(self, "选取文件", os.getcwd(), "所有文件 (*)")
|
|
if len(path) == 0: return
|
|
self.PCover, self.QCover = self.loadImage(path, self.labelCover)
|
|
|
|
def EtOpenSecret(self):
|
|
path, fileType = QtWidgets.QFileDialog.getOpenFileName(self, "选取文件", os.getcwd(), "所有文件 (*)")
|
|
if len(path) == 0: return
|
|
self.PSecret, self.QSecret = self.loadImage(path, self.labelSecret)
|
|
|
|
def EtOpenContainer(self):
|
|
path, fileType = QtWidgets.QFileDialog.getOpenFileName(self, "选取文件", os.getcwd(), "所有文件 (*)")
|
|
if len(path) == 0: return
|
|
self.PContainer, self.QContainer = self.loadImage(path, self.labelContainer)
|
|
|
|
def EtSaveCover(self):
|
|
dirpath = QtWidgets.QFileDialog.getSaveFileName(self, '选择保存路径', os.getcwd(), "所有文件 (*)")
|
|
print(dirpath[0])
|
|
self.PCover.save(dirpath[0])
|
|
|
|
def EtSaveSecret(self):
|
|
dirpath = QtWidgets.QFileDialog.getSaveFileName(self, '选择保存路径', os.getcwd(), "所有文件 (*)")
|
|
print(dirpath[0])
|
|
self.PSecret.save(dirpath[0])
|
|
|
|
def EtSaveContainer(self):
|
|
dirpath = QtWidgets.QFileDialog.getSaveFileName(self, '选择保存路径', os.getcwd(), "所有文件 (*)")
|
|
print(dirpath[0])
|
|
self.PContainer.save(dirpath[0])
|
|
|
|
def EtEncode(self):
|
|
# self.PContainer = self.PCover
|
|
self.PContainer = encode(self.PCover, self.PSecret)
|
|
self.setLabelImage(ImageQt.toqimage(self.PCover), self.labelContainer)
|
|
|
|
def EtDecode(self):
|
|
self.EtCleanCover()
|
|
self.PSecret = decode(self.PContainer)
|
|
# self.setLabelImage(ImageQt.toqimage(self.PCover), self.labelCover)
|
|
self.setLabelImage(ImageQt.toqimage(self.PSecret), self.labelSecret)
|
|
|
|
def loadImage(self, path: str, label: QtWidgets.QLabel):
|
|
p = Image.open(path)
|
|
q = ImageQt.toqimage(p)
|
|
self.setLabelImage(q, label)
|
|
return p,q
|
|
|
|
def setLabelImage(self, qimage: QtGui.QImage, label: QtWidgets.QLabel):
|
|
rect1 = label.rect()
|
|
rect2 = qimage.rect()
|
|
xscale = rect1.width() / rect2.width()
|
|
yscale = rect1.height() / rect2.height()
|
|
scale = min(xscale, yscale)
|
|
trans = QtGui.QTransform()
|
|
trans.scale(scale, scale)
|
|
q = qimage.transformed(trans)
|
|
label.setPixmap(QtGui.QPixmap.fromImage(q))
|
|
|
|
def add_leading_zeros(binary_number, expected_length):
|
|
"""
|
|
Adds leading zeros to a binary number so that the number of characters
|
|
in the binary string matches the specified expected length and the value
|
|
of the binary sring remains unchanged.
|
|
|
|
Args:
|
|
binary_number: A string representation of a number in base 2
|
|
expected_length: Expected length of the binary number string
|
|
|
|
Returns:
|
|
A binary string of a length specified by the argument with the
|
|
same numerical value as the binary number from the first argument.
|
|
"""
|
|
length = len(binary_number)
|
|
return (expected_length - length) * '0' + binary_number
|
|
|
|
def rgb_to_binary(r, g, b):
|
|
"""
|
|
Converts decimal numbers representing RGB values of a pixel into
|
|
binary numbers of the same values.
|
|
|
|
Args:
|
|
r: Decimal representation of the red channel value
|
|
g: Decimal representation of the green channel value
|
|
b: Decimal representation of the blue channel value
|
|
|
|
Returns:
|
|
Binary representations of the red, green, and blue channel values
|
|
"""
|
|
return add_leading_zeros(bin(r)[2:], 8), add_leading_zeros(bin(g)[2:], 8), add_leading_zeros(bin(b)[2:], 8)
|
|
|
|
def get_binary_pixel_values(img, width, height):
|
|
"""
|
|
Retrieves a string of concatenated binary representations of RGB channel values of all pixels in an image.
|
|
|
|
Args:
|
|
img: An RGB image
|
|
width: Width of the image
|
|
height: Height of the image
|
|
|
|
Returns:
|
|
A string with concatenated binary numbers representing the RGB channel values of all pixels in the image
|
|
where each binary number representing one channel value is 8 bits long, padded with leading zeros
|
|
when necessary. Therefore, each pixel in the image is represented by 24 bit long binary sequence.
|
|
"""
|
|
hidden_image_pixels = ''
|
|
for col in range(width):
|
|
for row in range(height):
|
|
pixel = img[col, row]
|
|
r = pixel[0]
|
|
g = pixel[1]
|
|
b = pixel[2]
|
|
r_binary, g_binary, b_binary = rgb_to_binary(r, g, b)
|
|
hidden_image_pixels += r_binary + g_binary + b_binary
|
|
return hidden_image_pixels
|
|
|
|
def change_binary_values(img_visible, hidden_image_pixels, width_visible, height_visible, width_hidden, height_hidden):
|
|
"""
|
|
Replaces the 4 least significant bits of a subset of pixels in an image with bits representing a sequence of binary
|
|
values of RGB channels of all pixels of the image to be concealed.
|
|
|
|
The first pixel in the top left corner is used to store the width and height of the image to be hidden, which is
|
|
necessary for recovery of the hidden image.
|
|
|
|
Args:
|
|
img_visible: An RGB image to be used for hiding another image
|
|
hidden_image_pixels: Binary string representing all pixel values of the image to be hidden
|
|
width_visible: Width of the image to be used for hiding another image
|
|
height_visible: Height of the image to be used for hiding another image
|
|
width_hidden: Width of the image to be hidden
|
|
height_hidden: Height of the image to be hidden
|
|
|
|
Returns:
|
|
An RGB image which is a copy of img_visible where the 4 least significant bits of a subset of pixels
|
|
are replaced with bits representing the hidden image.
|
|
"""
|
|
idx = 0
|
|
for col in range(width_visible):
|
|
for row in range(height_visible):
|
|
if row == 0 and col == 0:
|
|
width_hidden_binary = add_leading_zeros(bin(width_hidden)[2:], 12)
|
|
height_hidden_binary = add_leading_zeros(bin(height_hidden)[2:], 12)
|
|
w_h_binary = width_hidden_binary + height_hidden_binary
|
|
img_visible[col, row] = (int(w_h_binary[0:8], 2), int(w_h_binary[8:16], 2), int(w_h_binary[16:24], 2))
|
|
continue
|
|
r, g, b = img_visible[col, row]
|
|
r_binary, g_binary, b_binary = rgb_to_binary(r, g, b)
|
|
r_binary = r_binary[0:4] + hidden_image_pixels[idx:idx+4]
|
|
g_binary = g_binary[0:4] + hidden_image_pixels[idx+4:idx+8]
|
|
b_binary = b_binary[0:4] + hidden_image_pixels[idx+8:idx+12]
|
|
idx += 12
|
|
img_visible[col, row] = (int(r_binary, 2), int(g_binary, 2), int(b_binary, 2))
|
|
if idx >= len(hidden_image_pixels):
|
|
return img_visible
|
|
# can never be reached, but let's return the image anyway
|
|
return img_visible
|
|
|
|
def encode(img_visible, img_hidden):
|
|
"""
|
|
Loads the image to be hidden and the image used for hiding and conceals the pixel information from one image
|
|
in the other one.
|
|
|
|
Args:
|
|
img_visible: An RGB image used for hiding another image
|
|
img_hidden: An RGB image to be concealed
|
|
|
|
Returns:
|
|
An RGB image which is supposed to be not very different visually from img_visible, but contains all the information
|
|
necessary to recover an identical copy of the image we want to hide.
|
|
"""
|
|
encoded_image = img_visible.load()
|
|
img_hidden_copy = img_hidden.load()
|
|
width_visible, height_visible = img_visible.size
|
|
width_hidden, height_hidden = img_hidden.size
|
|
hidden_image_pixels = get_binary_pixel_values(img_hidden_copy, width_hidden, height_hidden)
|
|
encoded_image = change_binary_values(encoded_image, hidden_image_pixels, width_visible, height_visible, width_hidden, height_hidden)
|
|
return img_visible
|
|
|
|
|
|
def extract_hidden_pixels(image, width_visible, height_visible, pixel_count):
|
|
"""
|
|
Extracts a sequence of bits representing a sequence of binary values of
|
|
all pixels of the hidden image.
|
|
The information representing a hidden image is stored in the 4 least significant
|
|
bits of a subset of pixels of the visible image.
|
|
|
|
Args:
|
|
image: An RGB image to recover a hidden image from
|
|
width_visible: Width of the visible image
|
|
height_visible: Height of the visible image
|
|
pixel_count: Number of pixels in the hidden image
|
|
|
|
Returns:
|
|
A binary string representing pixel values of the hidden image
|
|
"""
|
|
hidden_image_pixels = ''
|
|
idx = 0
|
|
for col in range(width_visible):
|
|
for row in range(height_visible):
|
|
if row == 0 and col == 0:
|
|
continue
|
|
r, g, b = image[col, row]
|
|
r_binary, g_binary, b_binary = rgb_to_binary(r, g, b)
|
|
hidden_image_pixels += r_binary[4:8] + g_binary[4:8] + b_binary[4:8]
|
|
if idx >= pixel_count * 2:
|
|
return hidden_image_pixels
|
|
return hidden_image_pixels
|
|
|
|
def reconstruct_image(image_pixels, width, height):
|
|
"""
|
|
Recontructs the hidden image using the extracted string of pixel binary values.
|
|
|
|
Args:
|
|
image_pixels: A string of binary values of all pixels of the image to be recovered
|
|
width: Width of the image to be recovered
|
|
height: Height of the image to be recovered
|
|
|
|
Returns:
|
|
The recovered image
|
|
"""
|
|
image = Image.new("RGB", (width, height))
|
|
image_copy = image.load()
|
|
idx = 0
|
|
for col in range(width):
|
|
for row in range(height):
|
|
r_binary = image_pixels[idx:idx+8]
|
|
g_binary = image_pixels[idx+8:idx+16]
|
|
b_binary = image_pixels[idx+16:idx+24]
|
|
if len(r_binary) == 0: r_binary = '0'
|
|
if len(g_binary) == 0: g_binary = '0'
|
|
if len(b_binary) == 0: b_binary = '0'
|
|
# print(r_binary,g_binary,b_binary)
|
|
image_copy[col, row] = (int(r_binary, 2), int(g_binary, 2), int(b_binary, 2))
|
|
idx += 24
|
|
return image
|
|
|
|
def decode(image):
|
|
"""
|
|
Loads the image to recover a hidden image from, retrieves the information about the
|
|
size of the hidden image stored in the top left pixel of the visible image,
|
|
extracts the hidden binary pixel values from the image and reconstructs the hidden
|
|
image.
|
|
|
|
Args:
|
|
image: An RGB image to recover a hidden image from
|
|
|
|
Returns:
|
|
A recovered image, which was hidden in the binary representation of the visible image
|
|
"""
|
|
image_copy = image.load()
|
|
width_visible, height_visible = image.size
|
|
r, g, b = image_copy[0, 0]
|
|
r_binary, g_binary, b_binary = rgb_to_binary(r, g, b)
|
|
w_h_binary = r_binary + g_binary + b_binary
|
|
width_hidden = int(w_h_binary[0:12], 2)
|
|
height_hidden = int(w_h_binary[12:24], 2)
|
|
pixel_count = width_hidden * height_hidden
|
|
hidden_image_pixels = extract_hidden_pixels(image_copy, width_visible, height_visible, pixel_count)
|
|
decoded_image = reconstruct_image(hidden_image_pixels, width_hidden, height_hidden)
|
|
return decoded_image
|
|
|
|
if __name__ == '__main__':
|
|
app = QtWidgets.QApplication(sys.argv)
|
|
ui = UiMain()
|
|
sys.exit(app.exec_()) |