对前篇文章的代码进行了大量改动,做出了一个新玩具,虽然只能存储单个文件,但由于压缩文件这种类型的存在想存储多个文件也不是很麻烦
1、功能:(1)将单个文件隐藏于多张图片之中,并且选择图片时只需要选择带有图片的文件夹即可,图片不足时会自动循环使用图片
(2)从包含有生成图片的文件夹中解析出文件,并且可以在存在藏有不同文件的生成图的情况下解析出所有隐藏文件
(3)在以上过程中出现重名文件时会自动采取其他命名
2、交互界面及操作说明:
(1)初始交互界面:
下拉菜单选择模式
(2)生成图包:
载体图片文件夹:选择包含有图片的文件夹(代码里只写了".jpg",".png"两种图片,需 要采用其他支持本程序原理的图像类型修改代码即可)
对象文件:需要隐藏的文件
生成路径:生成的位置
生成名:生成图包文件夹的名字,若为空则默认为“对象文件的文件名去掉扩展名”
内置名:仅记录在生成的每张图片内部的隐藏名称,若为空则默认为“对象文件的MD5+系统时间”
(3)解析图包:
图包文件夹:包含需要解析的图片的文件夹
生成路径:生成的位置
3、注意:
(1)本篇代码写完后只在ubuntu上运行过,如果要在windows上可能需要修改代码
(2)生成名和内置名均存储于每张图片的内部,均用于识别隐藏的文件,需要两个图包混在 一起时不要把生成名和内置名均设为相同
4、原理:
单图片存储原理:通过将十六进制数据其转换成十进制隐藏于每个像素的B、G、R通道的最后一位(大于255则减10)来实现存储,存储结构为:
指令ID + 指令内容 + 指令ID + 指令内容+......
为方便读取存储内容,指令ID均使用大于255的数字。
多图片存储原理:每张图片开始写数据前写入一个标签指令,标签内容为图片序号、图包的生成名、图包的内置名,中间以999分割,以此实现多图片的识别与连接
5、代码:
(1)首先是开头的模块等:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import binascii
import time
from tkinter import ttk
import cv2
import numpy as np
import math as m
import os
from tkinter import *
from tkinter.filedialog import askdirectory, askopenfilename
import threading
import hashlib
(2)再是一些为了后续方便写在这里的函数,包括通过不同方式修改与提取像素、不同形式转换数据、判断名称是否存在、新建文件夹。
def split_int(s):
s = str(s)
n = len(s)
if n % 2 == 1:
s = '0' + s
n = len(s)
sl = np.zeros(int(n / 2))
i = 0
while i < (n / 2):
sl[i] = s[2 * i:2 * (i + 1)]
i += 1
return sl
def get_split_int(sl):
n = len(sl)
i = 0
while i < n:
if len(str(int(sl[i]))) % 2 == 1:
sl[i] = '0' + str(int(sl[i]))
else:
sl[i] = str(int(sl[i]))
i += 1
s = int(''.join(sl))
return s
def encode_bd(b):
b = binascii.hexlify(b)
b = str(b)
b = b[2:4]
d = int(b, 16)
return d
def encode_db(d):
d = str(hex(int(str(d), 10)))
d = d[2:4]
if len(d) == 1:
d = '0' + d
d = bytes(d, encoding='utf8')
b = binascii.unhexlify(d)
return b
def arr2str(arr):
j = 0
s1 = b''
while j < len(arr):
s = encode_db(int(arr[j]))
s1 = s1 + s
j = j + 1
s1 = s1.decode('utf-8')
return s1
def w_bgr(b, g, r, data):
data_r = data % 10
data_g = (data % 100 - data_r) / 10
data_b = (data - data_r - data_g) / 100
b = b - b % 10 + data_b
b = (b if b <= 255 else b - 10)
g = g - g % 10 + data_g
g = (g if g <= 255 else g - 10)
r = r - r % 10 + data_r
r = (r if r <= 255 else r - 10)
return b, g, r
def set_bgr(b, g, r, bgr_i, s_y, data):
x = int(m.floor(bgr_i / s_y))
y = int(bgr_i % s_y)
b[x, y], g[x, y], r[x, y] = w_bgr(b[x, y], g[x, y], r[x, y], data)
def get_d(b, g, r):
data = r % 10 + 10 * (g % 10) + 100 * (b % 10)
return data
def get_d_i(b, g, r, bgr_i, s_y):
x = int(m.floor(bgr_i / s_y))
y = int(bgr_i % s_y)
data = get_d(b[x, y], g[x, y], r[x, y])
return data
def get_byte(b, g, r, bgr_i, s_y):
x = int(m.floor(bgr_i / s_y))
y = int(bgr_i % s_y)
str_b = get_d(b[x, y], g[x, y], r[x, y])
str_b = encode_db(int(str_b))
return str_b
def name_exists(name, path=''):
if path != '' and path[-1] != '/':
path = path + '/'
j = 0
name = str(name)
name_new = name
while os.path.exists(path + name_new):
name_new = '(' + str(j) + ')' + name
j = j + 1
return name_new
def mkdir(name, path='./'):
if path != '' and path[-1] != '/':
path = path + '/'
name_new = name_exists(name, path)
print('创建文件夹' + name_new)
path = path + name_new
os.makedirs(path)
return path
(3)操作单个图片的类:
class IMG:
def __init__(self, path):
self.img = cv2.imread(path)
sp = self.img.shape
self.size = sp[0] * sp[1]
(self.b, self.g, self.r) = cv2.split(self.img)
def set_d(self, i, data):
s_y = self.img.shape[1]
set_bgr(self.b, self.g, self.r, i, s_y, data)
i += 1
return i
def set_b(self, i, data):
data = encode_bd(data)
s_y = self.img.shape[1]
set_bgr(self.b, self.g, self.r, i, s_y, data)
i += 1
return i
def set_ds(self, i, ds):
s_y = self.img.shape[1]
le = len(ds)
j = 0
while j < le:
set_bgr(self.b, self.g, self.r, i, s_y, ds[j])
j = j + 1
i += le
return i
def set_s(self, i, s):
s = str(s)
s = s.encode('utf-8')
le = len(s)
j = 0
while j < le:
k = s[j:(j + 1)]
k = binascii.hexlify(k)
k = int(k, 16)
i = self.set_d(i, k)
j = j + 1
return i
def update(self):
self.img = cv2.merge([self.b, self.g, self.r])
def save(self, name, path=''):
name = str(name)
print('生成文件:' + name + '.png', end=' ')
name = name + '.png'
new_name = name_exists(name, path)
if new_name != name:
print('文件已存在,更名为' + new_name)
else:
print('')
cv2.imwrite(path + new_name, self.img)
def get_d(self, i):
s_y = self.img.shape[1]
data = get_d_i(self.b, self.g, self.r, i, s_y)
i += 1
return data, i
def get_b(self, i):
s_y = self.img.shape[1]
b = get_byte(self.b, self.g, self.r, i, s_y)
i += 1
return b, i
(4)操作文件的类:
class FILE:
def __init__(self, path=''):
self.name = 'tank'
self.size = 0
if path != '' and path[-1] != '/':
path = path + '/'
self.path = path
(5)指令的定义、写指令、某些条件下调用来自动获取指令、读指令:
cmd_start = 256
cmd_filesize = 257
cmd_name = 258
cmd_file = 259
cmd_end = 260
p_cmd_tag = 261
cut = 999
def w_cmd(cmd, i, img, data):
if cmd == cmd_start:
print('写入开始指令...', end=' ')
i = img.set_d(i, cmd_start)
i = img.set_d(i, data)
print('完成,校验数:' + str(data))
return i
elif cmd == cmd_filesize:
print('写入文件大小...', end=' ')
d = data
i = img.set_d(i, cmd_filesize)
data = split_int(data)
i = img.set_ds(i, data)
print('完成,文件大小:' + str(d))
return i
elif cmd == cmd_name:
print('写入文件名称...', end=' ')
i = img.set_d(i, cmd_name)
name = os.path.split(data)
name = name[1]
i = img.set_s(i, name)
print('完成,文件名称:' + str(name))
return i
elif cmd == cmd_file:
print('写入文件...', end=' ')
i = img.set_d(i, cmd_file)
i0 = i
with open(data, "rb") as f:
while True:
strb = f.read(1)
if strb == b"":
print('完成')
return i
i = img.set_b(i, strb)
print('\r', '写入文件...已写入' + str(i - i0), end='', flush=True)
elif cmd == cmd_end:
print('写入结束指令...', end=' ')
i = img.set_d(i, cmd_end)
i = img.set_d(i, data)
print('完成')
return i
elif cmd == p_cmd_tag:
i = img.set_d(i, p_cmd_tag)
i = img.set_ds(i, split_int(data[1]))
i = img.set_d(i, cut)
i = img.set_s(i, data[2])
i = img.set_d(i, cut)
i = img.set_s(i, data[3])
i = img.set_d(i, cut)
return i
else:
print('指令错误,未识别的指令ID' + str(cmd))
return i
def auto_cmd(i, img):
cmd_list = []
data, i = img.get_d(i)
while data < 255:
cmd_list.append(data)
data, i = img.get_d(i)
i -= 1
return cmd_list, i
def r_cmd(i, img, file):
cmd, i = img.get_d(i)
if cmd == cmd_start:
print('读取开始指令...', end=' ')
data, i = img.get_d(i)
if data != 0:
i = -1
print('校验数:' + str(data) + ',错误')
else:
print('完成,校验数:' + str(data))
return i
elif cmd == cmd_filesize:
print('读取文件大小...', end=' ')
size, i = auto_cmd(i, img)
s = get_split_int(size)
file.size = s
print('完成,文件大小:' + str(s))
return i
elif cmd == cmd_name:
print('读取文件名...', end=' ')
data, i = auto_cmd(i, img)
s1 = arr2str(data)
file.name = s1
print('完成,文件名:' + str(s1), end=' ')
new_name = name_exists(str(s1), file.path)
if new_name != str(s1):
print('文件已存在,更名为' + new_name)
file.name = new_name
else:
print('')
return i
elif cmd == cmd_file:
print('读取文件,正在写入...', end=' ')
with open(file.path + file.name, "wb") as f:
j = 0
while j < file.size:
print('\r', '读取文件,正在写入...已写入' + str(j), end='', flush=True)
data, i = img.get_b(i)
f.write(data)
j += 1
print('\r', '读取文件,正在写入...已写入' + str(j), end='', flush=True)
print('完成')
return i
elif cmd == cmd_end:
print('读取结束指令...', end=' ')
i = -1
print('完成,结束读取')
return i
else:
i = -1
print('指令错误,未识别的指令ID:' + str(cmd))
return i
(6)读标签、获取文件列表的函数:
def read_p_tag(path):
img = IMG(path)
tag = []
i = 0
data, i = img.get_d(i)
if data == p_cmd_tag:
data, i = auto_cmd(i, img)
data = get_split_int(data)
tag.append(data)
data, i = img.get_d(i)
if data == cut:
data, i = auto_cmd(i, img)
s1 = arr2str(data)
tag.append(s1)
data, i = img.get_d(i)
if data == cut:
data, i = auto_cmd(i, img)
s1 = arr2str(data)
tag.append(s1)
data, i = img.get_d(i)
if data == cut:
return tag, i
else:
return -1, -1
else:
return -1, -1
else:
return -1, -1
else:
return -1, -1
def get_file_list(img_dir, file_list, ext=None):
if os.path.isfile(img_dir):
if ext is None:
file_list.append(img_dir)
else:
if ext in img_dir[-3:]:
file_list.append(img_dir)
elif os.path.isdir(img_dir):
for s in os.listdir(img_dir):
new_img_dir = os.path.join(img_dir, s)
get_file_list(new_img_dir, file_list, ext)
return file_list
(7)生成图包时使用的类:
class ImgPW:
def __init__(self, img_dir, name='0', int_name=0, save_path='./'):
self.name = name
self.int_name = int_name
self.img_list = []
self.img_i = 0
self.new_list = []
self.tag_sum = 0
self.path = mkdir(name, path=save_path)
self.img_list = get_file_list(img_dir, self.img_list, 'jpg') + get_file_list(img_dir, self.img_list, 'png')
self.new_list.append([self.img_list[0], 0, name, self.int_name])
self.img_img = IMG(self.img_list[0])
self.i_area = np.array([0, self.img_img.size])
self.tag_l = w_cmd(p_cmd_tag, 0, self.img_img, self.new_list[self.img_i])
self.tag_sum += self.tag_l
def change_img_i(self):
self.img_img.update()
self.save(self.name)
self.img_i = self.img_i + 1
self.img_img = IMG(self.img_list[(self.img_i % len(self.img_list))])
self.i_area = np.array([self.i_area[1], self.i_area[1] + self.img_img.size])
self.new_list.append(
[self.img_list[(self.img_i % len(self.img_list))], self.img_i, self.name, self.int_name])
i = w_cmd(p_cmd_tag, 0, self.img_img, self.new_list[self.img_i])
self.tag_l = i
self.tag_sum += self.tag_l
return i
def i_i(self, i):
if (i - self.i_area[0] + self.tag_sum) >= (self.i_area[1] - self.i_area[0]):
i = self.change_img_i()
return i
else:
i += - self.i_area[0] + self.tag_sum
return i
def set_d(self, i, data):
self.img_img.set_d(self.i_i(i), data)
i += 1
return i
def set_b(self, i, data):
self.img_img.set_b(self.i_i(i), data)
i += 1
return i
def set_ds(self, i, ds):
le = len(ds)
j = 0
while j < le:
s_y = self.img_img.img.shape[1]
set_bgr(self.img_img.b, self.img_img.g, self.img_img.r, self.i_i(i), s_y, ds[j])
j += 1
i += 1
return i
def set_s(self, i, s):
s = s.encode('utf-8')
le = len(s)
j = 0
while j < le:
k = s[j:(j + 1)]
k = binascii.hexlify(k)
k = int(k, 16)
i = self.set_d(i, k)
j += 1
return i
def save(self, name):
name = str(name)
path = self.path
if path != '' and path[-1] != '/':
path = path + '/'
print('生成文件:' + name + '.png', end=' ')
name = name + '.png'
new_name = name_exists(name, path)
if new_name != name:
print('文件已存在,更名为' + new_name)
else:
print('')
cv2.imwrite(path + new_name, self.img_img.img)
def end_save(self):
self.img_img.update()
self.save(self.name)
(8)解析图包时使用的类:
class ImgPR:
def __init__(self, img_dir):
self.img_list = []
self.img_list_s = []
self.other_list = []
self.img_i = 0
self.tag_sum = 0
self.img_list = get_file_list(img_dir, self.img_list, 'png')
l_i = 0
new_list_s = []
while l_i < len(self.img_list):
tag, tag_i = read_p_tag(self.img_list[l_i])
if tag != -1:
new_list_s.append([self.img_list[l_i]] + tag + [tag_i])
else:
print('未识别文件' + self.img_list[l_i])
l_i += 1
new_list_s = self.sort_list(new_list_s)
self.img_list = [x[0] for x in new_list_s]
self.img_list_s = new_list_s
self.img_img = IMG(self.img_list[0])
self.i_area = np.array([0, self.img_img.size])
self.tag_l = self.img_list_s[0][4]
self.tag_sum += self.tag_l
def sort_list(self, new_list_s):
other_list = []
name0 = new_list_s[0][2]
name1 = new_list_s[0][3]
i0 = 0
n_list_s = []
while i0 < (len(new_list_s)):
if new_list_s[i0][2] == name0 and new_list_s[i0][3] == name1:
n_list_s.append(new_list_s[i0])
else:
other_list.append(new_list_s[i0])
i0 += 1
self.other_list = other_list
i0 = 0
while i0 < (len(n_list_s) - 1):
j0 = 0
while j0 < (len(n_list_s) - 1 - i0):
if n_list_s[j0][1] > n_list_s[j0 + 1][1]:
n_list_s[j0], n_list_s[j0 + 1] = n_list_s[j0 + 1], n_list_s[j0]
j0 += 1
i0 += 1
if n_list_s[(len(n_list_s) - 1)][1] < (len(n_list_s) - 1):
j0 = 0
while j0 < (len(n_list_s) - 1):
if n_list_s[j0][1] == n_list_s[j0 + 1][1]:
print('存在生成名和内置名均相同的两个文件:' + str(n_list_s[j0][0]) + ' ' + str(n_list_s[j0 + 1][0]),
',不连接文件:' + str(n_list_s[j0][0]))
n_list_s.pop(j0)
j0 -= 1
j0 += 1
if n_list_s[(len(n_list_s) - 1)][1] > (len(n_list_s) - 1):
print('可能缺失文件,请小心')
return n_list_s
def next_list(self):
new_list_s = self.sort_list(self.other_list)
self.img_i = 0
self.tag_sum = 0
self.img_list = [x[0] for x in new_list_s]
self.img_list_s = new_list_s
self.img_img = IMG(self.img_list[0])
self.i_area = np.array([0, self.img_img.size])
self.tag_l = self.img_list_s[0][4]
self.tag_sum += self.tag_l
def change_img_i(self):
self.img_i += 1
self.img_img = IMG(self.img_list[(self.img_i % len(self.img_list))])
self.i_area = np.array([self.i_area[1], self.i_area[1] + self.img_img.size])
self.tag_l = self.img_list_s[self.img_i][4]
self.tag_sum += self.tag_l
return self.tag_l
def i_i(self, i):
if (i - self.i_area[0] + self.tag_sum) >= (self.i_area[1] - self.i_area[0]):
i = self.change_img_i()
return i
else:
i += - self.i_area[0] + self.tag_sum
return i
def get_d(self, i):
data, _ = self.img_img.get_d(self.i_i(i))
i += 1
return data, i
def get_b(self, i):
b, _ = self.img_img.get_b(self.i_i(i))
i += 1
return b, i
(9)与交互界面直接相关的函数及交互界面本身的运行:
def new_png_p(path, img_p, name='tank', int_name=0, save_path='./'):
img = ImgPW(img_p, name=name, int_name=int_name, save_path=save_path)
i = 0
i = w_cmd(cmd_start, i, img, 0)
i = w_cmd(cmd_filesize, i, img, os.path.getsize(path))
i = w_cmd(cmd_name, i, img, path)
i = w_cmd(cmd_file, i, img, path)
i = w_cmd(cmd_end, i, img, 0)
img.end_save()
sb10.configure(text='开始生成')
sb10.configure(state='normal')
sb2.configure(state='normal')
sb5.configure(state='normal')
sb5_2.configure(state='normal')
b0.configure(state='normal')
return i
def read_p(img, path=''):
img = ImgPR(img)
file = FILE(path)
i = 0
while i > -1:
while i > -1:
i = r_cmd(i, img, file)
if img.other_list:
img.next_list()
i = 0
lb6.configure(text='开始解析')
lb6.configure(state='normal')
lb2.configure(state='normal')
lb5.configure(state='normal')
b0.configure(state='normal')
def select_img_path():
path_ = askdirectory()
if path_ == "":
img_path.get()
else:
img_path.set(path_)
def select_file_path():
path_ = askopenfilename()
if path_ == "":
file_path.get()
else:
file_path.set(path_)
def select_n_path():
path_ = askdirectory()
if path_ == "":
n_path.get()
else:
n_path.set(path_)
def r_select_img_path():
path_ = askdirectory()
if path_ == "":
r_img_path.get()
else:
r_img_path.set(path_)
def r_select_file_path():
path_ = askdirectory()
if path_ == "":
r_file_path.get()
else:
r_file_path.set(path_)
def tk_save():
sb10.configure(text='生成中...')
sb10.configure(state='disabled')
b0.configure(state='disabled')
sb2.configure(state='disabled')
sb5.configure(state='disabled')
sb5_2.configure(state='disabled')
f_path = file_path.get()
i_path = img_path.get()
name = img_name.get()
if name == '':
_, name = os.path.split(f_path)
name, _ = os.path.splitext(name)
in_name = img_in_name.get()
if in_name == '':
d5 = hashlib.md5()
with open(f_path, 'rb') as f:
while True:
data = f.read(2024)
if not data:
break
d5.update(data)
md5 = d5.hexdigest()
in_name = str(md5) + str(time.time())
save_path = n_path.get()
if save_path == '':
save_path = './'
w_th = threading.Thread(target=new_png_p,
kwargs={"path": f_path, "img_p": i_path, "name": name, "int_name": in_name,
"save_path": save_path})
w_th.start()
def tk_load():
lb6.configure(text='解析中...')
lb6.configure(state='disabled')
b0.configure(state='disabled')
lb2.configure(state='disabled')
lb5.configure(state='disabled')
path = r_file_path.get()
i_path = r_img_path.get()
r_th = threading.Thread(target=read_p,
kwargs={"img": i_path, "path": path})
r_th.start()
def choose_mode():
c = number.get()
if c == '生成图包':
ll0.grid_forget()
le1.grid_forget()
lb2.grid_forget()
ll3.grid_forget()
le4.grid_forget()
lb5.grid_forget()
lb6.grid_forget()
sl0.grid(row=1, column=0)
se1.grid(row=1, column=1, ipadx=200, columnspan=5)
sb2.grid(row=1, column=6)
sl3.grid(row=2, column=0)
se4.grid(row=2, column=1, ipadx=200, columnspan=5)
sb5.grid(row=2, column=6)
sl5_0.grid(row=3, column=0)
se5_1.grid(row=3, column=1, ipadx=200, columnspan=5)
sb5_2.grid(row=3, column=6)
sl6.grid(row=4, column=0)
se7.grid(row=4, column=1, ipadx=200, columnspan=5)
sl8.grid(row=5, column=0)
se9.grid(row=5, column=1, ipadx=200, columnspan=5)
sb10.grid(row=6, column=0)
elif c == '解析图包':
sl0.grid_forget()
se1.grid_forget()
sb2.grid_forget()
sl3.grid_forget()
se4.grid_forget()
sb5.grid_forget()
sl5_0.grid_forget()
se5_1.grid_forget()
sb5_2.grid_forget()
sl6.grid_forget()
se7.grid_forget()
sl8.grid_forget()
se9.grid_forget()
sb10.grid_forget()
ll0.grid(row=1, column=0)
le1.grid(row=1, column=1, ipadx=200, columnspan=5)
lb2.grid(row=1, column=6)
ll3.grid(row=2, column=0)
le4.grid(row=2, column=1, ipadx=200, columnspan=5)
lb5.grid(row=2, column=6)
lb6.grid(row=3, column=0)
if __name__ == '__main__':
root = Tk()
root.title("py_tank by WTOte")
number = StringVar()
numberChosen = ttk.Combobox(root, width=12, textvariable=number, state="readonly")
numberChosen['values'] = ('生成图包', '解析图包')
numberChosen.grid(column=0, row=0)
numberChosen.current(0)
b0 = Button(root, text="选择模式", command=choose_mode)
b0.grid(row=0, column=1)
img_path = StringVar()
img_path.set(os.path.abspath("."))
file_path = StringVar()
file_path.set(os.path.abspath("."))
n_path = StringVar()
n_path.set(os.path.abspath("."))
sl0 = Label(root, text="载体图片文件夹:")
se1 = Entry(root, textvariable=img_path, state="readonly")
sb2 = Button(root, text="路径选择", command=select_img_path)
sl3 = Label(root, text="对象文件:")
se4 = Entry(root, textvariable=file_path, state="readonly")
sb5 = Button(root, text="路径选择", command=select_file_path)
sl5_0 = Label(root, text="生成路径:")
se5_1 = Entry(root, textvariable=n_path, state="readonly")
sb5_2 = Button(root, text="路径选择", command=select_n_path)
img_name = StringVar()
img_name.set('')
img_in_name = StringVar()
img_in_name.set('')
sl6 = Label(root, text="生成名(可缺省):")
se7 = Entry(root, textvariable=img_name)
sl8 = Label(root, text="内置名(可缺省):")
se9 = Entry(root, textvariable=img_in_name)
sb10 = Button(root, text="开始生成", command=tk_save)
r_img_path = StringVar()
r_img_path.set(os.path.abspath("."))
r_file_path = StringVar()
r_file_path.set(os.path.abspath("."))
ll0 = Label(root, text="图包文件夹:")
le1 = Entry(root, textvariable=r_img_path, state="readonly")
lb2 = Button(root, text="路径选择", command=r_select_img_path)
ll3 = Label(root, text="生成路径:")
le4 = Entry(root, textvariable=r_file_path, state="readonly")
lb5 = Button(root, text="路径选择", command=r_select_file_path)
lb6 = Button(root, text="开始解析", command=tk_load)
root.mainloop()
6、运行效果:
对象文件:
命令行输出:
生成图包:
解析后:
7、结语:程序写完后我自己运行起来没什么问题,玩得挺开心,但难免有所疏漏,还望大佬们不吝赐教。