forked from sentenza/GIMP-ELA
-
Notifications
You must be signed in to change notification settings - Fork 0
/
plugin-ela.py
executable file
·209 lines (179 loc) · 10.1 KB
/
plugin-ela.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#!/usr/bin/env python
# The MIT License (MIT)
# Copyright (c) 2012-2013 Alfredo Torre
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
# OR OTHER DEALINGS IN THE SOFTWARE.
# Error level analysis (ELA) shows differing error levels throughout an image,
# strongly suggesting some form of digital manipulation.
# ELA works by intentionally resaving the image at a known error rate.
from gimpfu import *
import os, sys, webbrowser
from datetime import datetime
plugin_version = 1.1
# create an output function that redirects to gimp's Error Console
def gprint( msg ):
pdb.gimp_message(msg)
return
## Print a HTML report.
def html_report(i, q, reportFile = "test.html") :
htmFile = open(reportFile, "w")
htmFile.write("""
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1">
<link rel="stylesheet" href="http://www.w3.org/StyleSheets/Core/Modernist" type="text/css">
<title>GIMP Error Level Analysis Report</title>
</head>
<body>
<h1>GIMP forensic analysis report</h1>
<p>This is an automated report generated by the
<a href="http://www.gimp.org/" target="_blank">GIMP</a> ELA forensic plugin on
<b>""")
adesso = datetime.now()
htmFile.write(adesso.strftime("%m/%d/%Y") + "</b> at <em>")
htmFile.write(adesso.strftime("%H:%M")+ "</em>.</p>")
# Print information about analyzed image
htmFile.write("<h2>Image information</h2>")
htmFile.write("<dl>")
htmFile.write("<dt>Image name</dt>")
htmFile.write("<dd>"+ i.filename +"</dd>")
htmFile.write("<dt>Image width</dt>")
htmFile.write("<dd>"+ str(i.width) +"</dd>")
htmFile.write("<dt>Image height</dt>")
htmFile.write("<dd>"+ str(i.height) +"</dd>")
htmFile.write("<dt>Quality of resaved JPEG image</dt>")
htmFile.write("<dd>"+ str(q) +"</dd>")
htmFile.write("</dl>")
# Print Version and date information
htmFile.write("<h2>Version information</h2>")
htmFile.write("<dl>")
htmFile.write("<dt>GIMP version</dt>")
htmFile.write("<dd>"+ str(gimp.pdb.gimp_version()) +"</dd>")
htmFile.write("<dt>JPEG ELA plugin version</dt>")
htmFile.write("<dd>"+ str(plugin_version) +"</dd>")
htmFile.write("</dl>")
htmFile.write("""
<h3>Reference</h3>
<h4>JPEG Error Level Analysis</h4>
<p>
<i>Error Level Analysis</i> (<acronym>ELA</acronym>) identifies areas within an image that are at different compression levels. With <a href="http://en.wikipedia.org/wiki/JPEG" target="_blank" title="Wikipedia definition">JPEG</a> images, the entire picture should be at roughly the same error level. If a section of the image is at a significantly different error level, then it likely indicates a digital modification.
</p>
<p>
JPEG images use a lossy compression system. Each re-encoding (resave) of the image adds more quality loss to the image. Specifically, the JPEG algorithm operates on an 8x8 pixel grid. Each 8x8 square is compressed independently. If the image is completely unmodified, then all 8x8 squares should have similar error potentials. If the image is unmodified and resaved, then every square should degrade at approximately the same rate.
ELA saves the image at a specified JPEG quality level. This resave introduces a known amount of error across the entire image. The resaved image is then compared against the original image.
If an image is modified, then every 8x8 square that was touched by the modification should be at a higher error potential than the rest of the image. Modified areas will appear with a higher potential error level.
</p>
<p>JPEG stores colors using the <acronym><a href="http://en.wikipedia.org/wiki/YUV" target="_blank" title="Wikipedia definition">YUV</a></acronym> color space. 'Y' is the luminance, or gray-scale intensity of the image, 'U' and 'V' are the chrominance-blue and chrominance-red color portions. For displaying, the JPEG decoder converts the image from YUV to RGB.
JPEG always encodes luminance with an 8x8 grid. However, chrominance may be encoded using 8x8, 8x16, 16x8, or 16x16. The chrominance subsampling is a JPEG encoding option. Depending on the selected chrominance subsampling, each 8x8, 8x16, 16x8, 16x16 grid will be independently encoded.</p>
<h5>Analysis</h5>
<p>With ELA, every grid that is not optimized for the quality level will show grid squares that change during a resave. For example, digital cameras do not optimize images for the specified camera quality level (high, medium, low, etc.). Original pictures from digital cameras should have a high degree of change during any resave (high ELA values). However, an unmodified digital photo that has been resaved will have lower ELA values. In contrast, if the grid square is already at its minimum error level, then it will not change during the resave.</p><p class="ltb"> </p>
<h2>Authors of the plugin</h2>
<dl>
<dt>Alfredo Torre</dt>
<dd><a href="https://twitter.com/skydiamond">@skydiamond</a></dd>
</dl>
<address>
Department of Electrical, Electronic and Computer Engineering. University of Catania, Italy.<br>
<a href="http://www.dieei.unict.it/">www.dieei.unict.it</a>
</address>
""")
htmFile.write("""</body></html>""")
htmFile.close
webbrowser.open(reportFile, new=2)
return
def error_level_analysis(img, quality, report, reportFile) :
g = gimp.pdb
#<M-T-F2>img = gimp.image_list()[0]
layers = img.layers
tmpfile = "error-level-analysis-tmp.jpg"
if (img.filename == None) :
gprint("Error. The image must be stored before applying ELA.")
return
gimp.progress_init("Analyzing error levels on " + img.filename )
# Separate contectual changes (brush, colors, etc.) from the user
gimp.context_push()
# Allow all these changes to apprear as 1 atomic action (1 undo)
img.undo_group_start()
# Set up an undo group, so the operation will be undone in one step.
#g.gimp_undo_push_group_start(img) # DEPRECATED
# Creating a temporary image from the current one
img_tmp = g.gimp_image_duplicate(img)
# Merge the layers which are visible into a single layer
# The final layer is large enough to contain all of the merged layers.
g.gimp_image_merge_visible_layers(img_tmp, EXPAND_AS_NECESSARY)
# get the current drawable ID, because the img-tmp is in a new layer
draw_tmp = g.gimp_image_get_active_drawable(img_tmp)
g.file_jpeg_save(img_tmp, draw_tmp, tmpfile, tmpfile, quality, 0, 0, 0, "GIMP ELA Temporary Image", 0, 0, 0, 0)
draw_tmp = g.gimp_file_load_layer(img_tmp, tmpfile)
# create new layer; syntax image layer parent_layer position
#g.gimp_image_insert_layer(img_tmp, draw_tmp, 0, -1)
img_tmp.add_layer(draw_tmp, 0)
# Set the combination mode of the specified layer.
# syntax: lager combination_mode.
# absolute value of the difference between the highest layer and the one immediately below.
g.gimp_layer_set_mode(draw_tmp, DIFFERENCE_MODE)
os.remove(tmpfile)
g.gimp_edit_copy_visible(img_tmp)
error_layer = g.gimp_layer_new_from_visible(img_tmp, img_tmp, "Error Levels")
#g.gimp_image_insert_layer(img_tmp, error_layer, 0, -1)
img_tmp.add_layer(error_layer, 0)
# Automatically modifies intensity levels in the specified drawable(layer)
g.gimp_levels_stretch(error_layer)
# Only for testing purposes
#g.gimp_display_new(img_tmp)
# ADD ERROR LEVELS AS A NEW LAYER ON THE ORIGINAL IMAGE
g.gimp_edit_copy_visible(img_tmp)
# Create and return a new layer from what is visible in an image. source_img dest_img
error_layer = g.gimp_layer_new_from_visible(img_tmp, img, "Error Levels")
img.add_layer(error_layer, 0)
if(report) :
if(reportFile != None) :
html_report(img, quality, reportFile)
else :
html_report(img, quality)
# Finish the undo group.
#g.gimp_undo_push_group_end(img) # DEPRECATED
# Leave the user in the same context they were in before
img.undo_group_end()
# Return the user to the context they were in before
gimp.context_pop()
# Flush all internal changes to the user interface
# g.gimp_displays_flush
return
register(
"python_fu_error_level_analysis",
"JPEG Error Level Analysis. Set the quality of the resaved JPEG image between 0 and 1 to calculate the difference layer. Therefore, you could choose to produce a HTML record file in your " +
" user's home directory.",
"JPEG Error level analysis (ELA) shows differing error levels throughout an image, strongly suggesting some form of digital manipulation.",
"Alfredo Torre",
"Alfredo Torre",
"September 2012",
"JPEG ELA",
"*", # Alternately use RGB, RGB*, GRAY*, INDEXED etc.
[
(PF_IMAGE, "img", "Input image", None),
(PF_SLIDER, "quality", "_Quality", 0.7, (0, 1, 0.01)),
(PF_BOOL, "report", "Create HTML report", True),
(PF_STRING, "reportName", "R_eport name", "Report_JPEG_ELA.html")
],
[],
error_level_analysis,
menu="<Image>/Filters/Forensics"
)
main()