AnsenH commited on
Commit
de08da3
·
1 Parent(s): 62b38ae
Files changed (4) hide show
  1. app.py +102 -0
  2. requirement.txt +4 -0
  3. steganography.py +127 -0
  4. utils.py +25 -0
app.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from steganography import Steganography
3
+ from utils import draw_multiple_line_text, generate_qr_code
4
+
5
+
6
+ TITLE = """<h2 align="center"> ✍️ Invisible Watermark </h2>"""
7
+
8
+
9
+ def apply_watermark(radio_button, input_image, watermark_image, watermark_text, watermark_url):
10
+ input_image = input_image.convert('RGB')
11
+
12
+ if radio_button == "Image":
13
+ watermark_image = watermark_image.resize((input_image.width, input_image.height)).convert('L').convert('RGB')
14
+ return Steganography().merge(input_image, watermark_image, digit=7)
15
+ elif radio_button == "Text":
16
+ watermark_image = draw_multiple_line_text(input_image.size, watermark_text)
17
+ return Steganography().merge(input_image, watermark_image, digit=7)
18
+ else:
19
+ size = min(input_image.width, input_image.height)
20
+ watermark_image = generate_qr_code(watermark_url).resize((size, size)).convert('RGB')
21
+ return Steganography().merge(input_image, watermark_image, digit=7)
22
+
23
+ def extract_watermark(input_image_to_extract):
24
+ return Steganography().unmerge(input_image_to_extract.convert('RGB'), digit=7).convert('RGBA')
25
+
26
+
27
+ with gr.Blocks() as demo:
28
+ gr.HTML(TITLE)
29
+ with gr.Tab("Add watermark"):
30
+ with gr.Row():
31
+ with gr.Column():
32
+ gr.Markdown("### Image to apply watermark")
33
+ input_image = gr.Image(type='pil')
34
+ with gr.Blocks():
35
+ gr.Markdown("### Which type of watermark you want to apply?")
36
+ radio_button = gr.Radio(
37
+ choices=["QRCode", "Text", "Image"],
38
+ label="Watermark type",
39
+ value="QRCode",
40
+ # info="Which type of watermark you want to apply?"
41
+ )
42
+ watermark_url = gr.Textbox(
43
+ placeholder="URL to generate QR code",
44
+ visible=True
45
+ )
46
+ watermark_text = gr.Textbox(
47
+ placeholder="What text you want to use as watermark?",
48
+ visible=False
49
+ )
50
+ watermark_image = gr.Image(
51
+ type='pil',
52
+ visible=False
53
+ )
54
+
55
+ def update_visability(radio_value):
56
+ return {
57
+ watermark_image:
58
+ {
59
+ "visible":radio_value == "Image",
60
+ "__type__": "update"
61
+ },
62
+ watermark_text:
63
+ {
64
+ "visible":radio_value == "Text",
65
+ "__type__": "update"
66
+ },
67
+ watermark_url:
68
+ {
69
+ "visible":radio_value == "QRCode",
70
+ "__type__": "update"
71
+ }
72
+ }
73
+
74
+ with gr.Column():
75
+ gr.Markdown("### Appied watermark image")
76
+ output_image = gr.Image(show_label=False)
77
+ with gr.Row():
78
+ apply_button =gr.Button("Apply")
79
+
80
+ with gr.Tab("Extract watermark"):
81
+ with gr.Row():
82
+ with gr.Column():
83
+ gr.Markdown("### Image to extract watermark")
84
+ input_image_to_extract = gr.Image(type='pil')
85
+ with gr.Column():
86
+ gr.Markdown("### Extracted watermark")
87
+ extracted_watermark = gr.Image(type='pil')
88
+ extract_button = gr.Button("Extract")
89
+
90
+ radio_button.change(
91
+ fn=update_visability,
92
+ inputs=radio_button,
93
+ outputs=[watermark_image, watermark_text, watermark_url]
94
+ )
95
+ apply_button.click(
96
+ fn=apply_watermark,
97
+ inputs=[radio_button, input_image, watermark_image, watermark_text, watermark_url],
98
+ outputs=[output_image]
99
+ )
100
+ extract_button.click(fn=extract_watermark, inputs=[input_image_to_extract], outputs=[extracted_watermark])
101
+
102
+ demo.launch()
requirement.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ Pillow
2
+ click
3
+ gradio
4
+ qrcode
steganography.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+
3
+ from PIL import Image
4
+
5
+
6
+ class Steganography:
7
+
8
+ BLACK_PIXEL = (0, 0, 0)
9
+
10
+ def _int_to_bin(self, rgb):
11
+ """Convert an integer tuple to a binary (string) tuple.
12
+
13
+ :param rgb: An integer tuple like (220, 110, 96)
14
+ :return: A string tuple like ("00101010", "11101011", "00010110")
15
+ """
16
+ r, g, b = rgb
17
+ return f'{r:08b}', f'{g:08b}', f'{b:08b}'
18
+
19
+ def _bin_to_int(self, rgb):
20
+ """Convert a binary (string) tuple to an integer tuple.
21
+
22
+ :param rgb: A string tuple like ("00101010", "11101011", "00010110")
23
+ :return: Return an int tuple like (220, 110, 96)
24
+ """
25
+ r, g, b = rgb
26
+ return int(r, 2), int(g, 2), int(b, 2)
27
+
28
+ def _merge_rgb(self, rgb1, rgb2, digit):
29
+ """Merge two RGB tuples.
30
+
31
+ :param rgb1: An integer tuple like (220, 110, 96)
32
+ :param rgb2: An integer tuple like (240, 95, 105)
33
+ :return: An integer tuple with the two RGB values merged.
34
+ """
35
+ r1, g1, b1 = self._int_to_bin(rgb1)
36
+ r2, g2, b2 = self._int_to_bin(rgb2)
37
+ rgb = r1[:digit] + r2[:8-digit], g1[:digit] + g2[:8-digit], b1[:digit] + b2[:8-digit]
38
+ return self._bin_to_int(rgb)
39
+
40
+ def _unmerge_rgb(self, rgb, digit):
41
+ """Unmerge RGB.
42
+
43
+ :param rgb: An integer tuple like (220, 110, 96)
44
+ :return: An integer tuple with the two RGB values merged.
45
+ """
46
+ r, g, b = self._int_to_bin(rgb)
47
+ # Extract the last 4 bits (corresponding to the hidden image)
48
+ # Concatenate 4 zero bits because we are working with 8 bit
49
+ new_rgb = r[digit:] + '0'*digit, g[digit:] + '0'*digit, b[digit:] + '0'*digit
50
+ return self._bin_to_int(new_rgb)
51
+
52
+ def merge(self, image1, image2, digit=4):
53
+ """Merge image2 into image1.
54
+
55
+ :param image1: First image
56
+ :param image2: Second image
57
+ :return: A new merged image.
58
+ """
59
+ # Check the images dimensions
60
+ if image2.size[0] > image1.size[0] or image2.size[1] > image1.size[1]:
61
+ raise ValueError('Image 2 should be smaller than Image 1!')
62
+
63
+ # Get the pixel map of the two images
64
+ map1 = image1.load()
65
+ map2 = image2.load()
66
+
67
+ new_image = Image.new(image1.mode, image1.size)
68
+ new_map = new_image.load()
69
+
70
+ for i in range(image1.size[0]):
71
+ for j in range(image1.size[1]):
72
+ is_valid = lambda: i < image2.size[0] and j < image2.size[1]
73
+ rgb1 = map1[i ,j]
74
+ rgb2 = map2[i, j] if is_valid() else self.BLACK_PIXEL
75
+ new_map[i, j] = self._merge_rgb(rgb1, rgb2, digit)
76
+
77
+ return new_image
78
+
79
+ def unmerge(self, image, digit=4, binarization=True):
80
+ """Unmerge an image.
81
+
82
+ :param image: The input image.
83
+ :return: The unmerged/extracted image.
84
+ """
85
+ pixel_map = image.load()
86
+
87
+ # Create the new image and load the pixel map
88
+ new_image = Image.new(image.mode, image.size)
89
+ new_map = new_image.load()
90
+
91
+ for i in range(image.size[0]):
92
+ for j in range(image.size[1]):
93
+ r, g, b = self._unmerge_rgb(pixel_map[i, j], digit)
94
+ r = 255 if r >= 128 else 0
95
+ g = 255 if g >= 128 else 0
96
+ b = 255 if b >= 128 else 0
97
+ new_map[i, j] = r, g, b
98
+
99
+ return new_image
100
+
101
+
102
+ def main():
103
+ parser = argparse.ArgumentParser(description='Steganography')
104
+ subparser = parser.add_subparsers(dest='command')
105
+
106
+ merge = subparser.add_parser('merge')
107
+ merge.add_argument('--image1', required=True, help='Image1 path')
108
+ merge.add_argument('--image2', required=True, help='Image2 path')
109
+ merge.add_argument('--output', required=True, help='Output path')
110
+
111
+ unmerge = subparser.add_parser('unmerge')
112
+ unmerge.add_argument('--image', required=True, help='Image path')
113
+ unmerge.add_argument('--output', required=True, help='Output path')
114
+
115
+ args = parser.parse_args()
116
+
117
+ if args.command == 'merge':
118
+ image1 = Image.open(args.image1)
119
+ image2 = Image.open(args.image2)
120
+ Steganography().merge(image1, image2).save(args.output)
121
+ elif args.command == 'unmerge':
122
+ image = Image.open(args.image)
123
+ Steganography().unmerge(image).save(args.output)
124
+
125
+
126
+ if __name__ == '__main__':
127
+ main()
utils.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from PIL import Image, ImageDraw, ImageFont
2
+ import qrcode
3
+
4
+ def generate_qr_code(url):
5
+ return qrcode.make(url)
6
+
7
+ def draw_multiple_line_text(input_image_size, text, font=None, text_color=(255, 255, 255)):
8
+ if font is None:
9
+ font = ImageFont.load_default()
10
+ watermark_image = Image.new("RGB", input_image_size, (0, 0, 0))
11
+ output_image = watermark_image.copy()
12
+ draw = ImageDraw.Draw(watermark_image)
13
+ image_width, image_height = input_image_size
14
+ line_width, line_height = font.getsize(text)
15
+ draw.text(
16
+ ((image_width - line_width)/2, (image_height - line_height)/2),
17
+ text,
18
+ font=font,
19
+ fill=text_color
20
+ )
21
+
22
+ scale = min(image_width / line_width, image_height / line_height)
23
+ watermark_image = watermark_image.resize((int(watermark_image.width * scale), int(watermark_image.height*scale)))
24
+ output_image.paste(watermark_image, (int((image_width-watermark_image.width)/2), int((image_height-watermark_image.height)/2)))
25
+ return output_image