imdiff 0.4.2__tar.gz → 0.4.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {imdiff-0.4.2 → imdiff-0.4.4}/PKG-INFO +1 -1
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff/gui/file_list_frame.py +5 -3
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff/gui/image_canvas.py +28 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff/gui/image_frame.py +108 -67
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff/gui/zoom_menu.py +3 -3
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff/version.py +1 -1
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff.egg-info/PKG-INFO +1 -1
- {imdiff-0.4.2 → imdiff-0.4.4}/LICENSE +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/README.md +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff/__init__.py +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff/__main__.py +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff/cli/__init__.py +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff/cli/dir_diff.py +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff/cli/image_diff.py +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff/cli/main.py +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff/directory_comparator.py +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff/gui/__init__.py +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff/gui/dir_diff_main_window.py +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff/gui/image_diff_main_window.py +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff/gui/image_scaling.py +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff/gui/status_bar.py +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff/gui/transient_menu.py +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff/image_comparator.py +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff/list_files.py +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff/util.py +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff.egg-info/SOURCES.txt +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff.egg-info/dependency_links.txt +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff.egg-info/entry_points.txt +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff.egg-info/requires.txt +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/imdiff.egg-info/top_level.txt +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/pyproject.toml +0 -0
- {imdiff-0.4.2 → imdiff-0.4.4}/setup.cfg +0 -0
|
@@ -270,7 +270,7 @@ class FileListFrame(ttk.Frame):
|
|
|
270
270
|
diff_info = 'similar' if nrmse < 0.02 else 'different'
|
|
271
271
|
category = '~' if nrmse < 0.02 else 'D'
|
|
272
272
|
|
|
273
|
-
return file_path, category, diff_info
|
|
273
|
+
return file_path, category, diff_info, comparator
|
|
274
274
|
|
|
275
275
|
@separate_thread
|
|
276
276
|
def process_files(self):
|
|
@@ -286,7 +286,8 @@ class FileListFrame(ttk.Frame):
|
|
|
286
286
|
jobs.append(executor.submit(
|
|
287
287
|
FileListFrame.get_diff_info, file_path, self.files[file_path]))
|
|
288
288
|
for job in concurrent.futures.as_completed(jobs):
|
|
289
|
-
file_path, category, diff_info = job.result()
|
|
289
|
+
file_path, category, diff_info, icmp = job.result()
|
|
290
|
+
self.files[file_path] = icmp
|
|
290
291
|
self.file_properties_queue.put((file_path, category, diff_info))
|
|
291
292
|
self.file_properties_queue.status = IterableQueue.Status.Complete
|
|
292
293
|
finally:
|
|
@@ -393,7 +394,8 @@ class FileListFrame(ttk.Frame):
|
|
|
393
394
|
return
|
|
394
395
|
|
|
395
396
|
res = FileListFrame.get_diff_info(file_path, self.files[file_path])
|
|
396
|
-
file_path, category, diff_info = res
|
|
397
|
+
file_path, category, diff_info, icmp = res
|
|
398
|
+
self.files[file_path] = icmp
|
|
397
399
|
self.set_file_properties(file_path, category, diff_info)
|
|
398
400
|
|
|
399
401
|
def sort_filepaths(self, nparts, reverse):
|
|
@@ -61,6 +61,31 @@ class ImageCanvas(tk.Canvas):
|
|
|
61
61
|
self.image = image
|
|
62
62
|
self.after_idle(self.update_image)
|
|
63
63
|
|
|
64
|
+
def swap_image(self, image):
|
|
65
|
+
"""Swap displayed image without clearing canvas, to avoid flicker."""
|
|
66
|
+
log.debug(f'[{self.label}] swap image (setting to {image})')
|
|
67
|
+
if image is None:
|
|
68
|
+
self.clear()
|
|
69
|
+
return
|
|
70
|
+
self.image = image
|
|
71
|
+
# Reset scaled image to force recalculation
|
|
72
|
+
self.scaled_image = None
|
|
73
|
+
# Synchronously update display
|
|
74
|
+
self.update_scaled_image()
|
|
75
|
+
# Create PhotoImage before modifying canvas
|
|
76
|
+
if self.scaled_image:
|
|
77
|
+
new_tkimage = ImageTk.PhotoImage(self.scaled_image)
|
|
78
|
+
if self.image_id:
|
|
79
|
+
# Update existing canvas item - this is atomic and won't flicker
|
|
80
|
+
self.itemconfig(self.image_id, image=new_tkimage)
|
|
81
|
+
else:
|
|
82
|
+
self.image_id = self.create_image(
|
|
83
|
+
self.border, self.border, image=new_tkimage, anchor='nw'
|
|
84
|
+
)
|
|
85
|
+
# Keep reference to prevent garbage collection
|
|
86
|
+
self.tkimage = new_tkimage
|
|
87
|
+
self.update_layout()
|
|
88
|
+
|
|
64
89
|
def update_scaled_image(self):
|
|
65
90
|
log.debug(f'[{self.label}] update scaled image (image: {self.image})')
|
|
66
91
|
self.scaled_image_updated = False
|
|
@@ -155,3 +180,6 @@ class ImageCanvas(tk.Canvas):
|
|
|
155
180
|
if pos is not None:
|
|
156
181
|
self.position = pos
|
|
157
182
|
self.update_image()
|
|
183
|
+
|
|
184
|
+
def raise_to_front(self):
|
|
185
|
+
self.tk.call('raise', self)
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import platform
|
|
3
|
-
import multiprocessing
|
|
4
|
-
import time
|
|
5
3
|
|
|
6
4
|
import tkinter as tk
|
|
7
5
|
import ttkbootstrap as ttk
|
|
@@ -80,11 +78,18 @@ class ImageFrame(ttk.Frame):
|
|
|
80
78
|
)
|
|
81
79
|
|
|
82
80
|
self.image_frame = ttk.Frame(self)
|
|
83
|
-
self.
|
|
81
|
+
self.canvases = {
|
|
84
82
|
'left': ImageCanvas(self.image_frame, 'left'),
|
|
85
83
|
'right': ImageCanvas(self.image_frame, 'right'),
|
|
86
84
|
'diff': ImageCanvas(self.image_frame, 'diff'),
|
|
87
85
|
}
|
|
86
|
+
self.images = {
|
|
87
|
+
'left': None,
|
|
88
|
+
'right': None,
|
|
89
|
+
'diff': None,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
self.current_top_image = 'left'
|
|
88
93
|
|
|
89
94
|
self.comparator = None
|
|
90
95
|
self.button_zoom_fit_if_larger.invoke()
|
|
@@ -145,15 +150,15 @@ class ImageFrame(ttk.Frame):
|
|
|
145
150
|
|
|
146
151
|
def layout_images(self):
|
|
147
152
|
if platform.system() == 'Linux':
|
|
148
|
-
mouse_wheel_seqs = ('<Button-4>', '<Button-5>')
|
|
153
|
+
mouse_wheel_seqs = ('<Button-4>', '<Button-5>', '<MouseWheel>',)
|
|
149
154
|
else:
|
|
150
155
|
mouse_wheel_seqs = ('<MouseWheel>',)
|
|
151
156
|
|
|
152
157
|
n = self.image_layout_style.get()
|
|
153
158
|
if n == 3:
|
|
154
|
-
self.
|
|
155
|
-
self.
|
|
156
|
-
self.
|
|
159
|
+
self.canvases['left'].grid(column=0, row=0, sticky='nsew')
|
|
160
|
+
self.canvases['right'].grid(column=1, row=0, sticky='nsew')
|
|
161
|
+
self.canvases['diff'].grid(column=2, row=0, sticky='nsew')
|
|
157
162
|
|
|
158
163
|
self.image_frame.columnconfigure(0, weight=1)
|
|
159
164
|
self.image_frame.columnconfigure(1, weight=1)
|
|
@@ -161,78 +166,81 @@ class ImageFrame(ttk.Frame):
|
|
|
161
166
|
|
|
162
167
|
for seq in mouse_wheel_seqs:
|
|
163
168
|
self.image_frame.unbind(seq)
|
|
164
|
-
for
|
|
165
|
-
|
|
169
|
+
for canvas in self.canvases.values():
|
|
170
|
+
canvas.unbind(seq)
|
|
166
171
|
|
|
167
172
|
elif n == 2:
|
|
168
|
-
|
|
169
|
-
self.
|
|
170
|
-
self.
|
|
173
|
+
# Use single 'left' canvas for swapping left/right, plus diff canvas
|
|
174
|
+
self.canvases['left'].grid(column=0, row=0, sticky='nsew')
|
|
175
|
+
self.canvases['right'].grid_forget()
|
|
176
|
+
self.canvases['diff'].grid(column=1, row=0, sticky='nsew')
|
|
171
177
|
|
|
172
178
|
self.image_frame.columnconfigure(0, weight=1)
|
|
173
179
|
self.image_frame.columnconfigure(1, weight=1)
|
|
174
180
|
self.image_frame.columnconfigure(2, weight=0)
|
|
175
181
|
|
|
182
|
+
if self.current_top_image not in ('left', 'right'):
|
|
183
|
+
self.current_top_image = 'left'
|
|
184
|
+
self.canvases['left'].swap_image(self.images[self.current_top_image])
|
|
185
|
+
|
|
176
186
|
for seq in mouse_wheel_seqs:
|
|
177
187
|
self.image_frame.bind(seq, self.on_mouse_wheel)
|
|
178
|
-
for
|
|
179
|
-
|
|
188
|
+
for canvas in self.canvases.values():
|
|
189
|
+
canvas.bind(seq, self.on_mouse_wheel, add='+')
|
|
180
190
|
|
|
181
191
|
else:
|
|
182
192
|
assert n == 1
|
|
183
|
-
|
|
184
|
-
self.
|
|
185
|
-
self.
|
|
193
|
+
# Use single 'left' canvas for swapping all three images
|
|
194
|
+
self.canvases['left'].grid(column=0, row=0, sticky='nsew')
|
|
195
|
+
self.canvases['right'].grid_forget()
|
|
196
|
+
self.canvases['diff'].grid_forget()
|
|
186
197
|
|
|
187
198
|
self.image_frame.columnconfigure(0, weight=1)
|
|
188
199
|
self.image_frame.columnconfigure(1, weight=0)
|
|
189
200
|
self.image_frame.columnconfigure(2, weight=0)
|
|
190
201
|
|
|
202
|
+
self.canvases['left'].swap_image(self.images[self.current_top_image])
|
|
203
|
+
|
|
191
204
|
for seq in mouse_wheel_seqs:
|
|
192
205
|
self.image_frame.bind(seq, self.on_mouse_wheel)
|
|
193
|
-
for
|
|
194
|
-
|
|
206
|
+
for canvas in self.canvases.values():
|
|
207
|
+
canvas.bind(seq, self.on_mouse_wheel, add='+')
|
|
195
208
|
|
|
196
209
|
def bind_events(self):
|
|
197
|
-
for key in self.
|
|
198
|
-
for otherkey in set(self.
|
|
199
|
-
self.
|
|
200
|
-
'<Button1-Motion>', self.
|
|
210
|
+
for key in self.canvases.keys():
|
|
211
|
+
for otherkey in set(self.canvases.keys()) - {key}:
|
|
212
|
+
self.canvases[key].bind(
|
|
213
|
+
'<Button1-Motion>', self.canvases[otherkey].on_motion, add='+'
|
|
201
214
|
)
|
|
202
|
-
self.
|
|
203
|
-
'<ButtonRelease-1>', self.
|
|
215
|
+
self.canvases[key].bind(
|
|
216
|
+
'<ButtonRelease-1>', self.canvases[otherkey].on_left_up, add='+'
|
|
204
217
|
)
|
|
205
218
|
|
|
206
219
|
def on_mouse_wheel(self, evt):
|
|
207
|
-
if
|
|
208
|
-
mouse_wheel_up = evt.num == 5
|
|
209
|
-
else:
|
|
220
|
+
if evt.type == tk.EventType.MouseWheel:
|
|
210
221
|
mouse_wheel_up = evt.delta > 0
|
|
222
|
+
else:
|
|
223
|
+
mouse_wheel_up = evt.num == 5
|
|
211
224
|
|
|
212
225
|
n = self.image_layout_style.get()
|
|
213
226
|
if n == 2:
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
self.
|
|
227
|
+
# Toggle between left and right
|
|
228
|
+
if self.current_top_image == 'left':
|
|
229
|
+
self.current_top_image = 'right'
|
|
217
230
|
else:
|
|
218
|
-
|
|
219
|
-
self.images['left'].grid(column=0, row=0, sticky='nsew')
|
|
220
|
-
self.images['right'].grid_forget()
|
|
231
|
+
self.current_top_image = 'left'
|
|
221
232
|
else:
|
|
222
233
|
assert n == 1
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
next_image = 'diff' if mouse_wheel_up else 'left'
|
|
229
|
-
self.images['right'].grid_forget()
|
|
230
|
-
self.images[next_image].grid(column=0, row=0, sticky='nsew')
|
|
234
|
+
# Cycle through left -> right -> diff -> left (or reverse)
|
|
235
|
+
cycle = ['left', 'right', 'diff']
|
|
236
|
+
current_idx = cycle.index(self.current_top_image)
|
|
237
|
+
if mouse_wheel_up:
|
|
238
|
+
next_idx = (current_idx + 1) % 3
|
|
231
239
|
else:
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
240
|
+
next_idx = (current_idx - 1) % 3
|
|
241
|
+
self.current_top_image = cycle[next_idx]
|
|
242
|
+
|
|
243
|
+
self.canvases['left'].swap_image(self.images[self.current_top_image])
|
|
236
244
|
|
|
237
245
|
def minsize(self):
|
|
238
246
|
width = (
|
|
@@ -251,8 +259,8 @@ class ImageFrame(ttk.Frame):
|
|
|
251
259
|
self.zoom(ImageScaling.Mode.Fit, factor=1, shrink=True, expand=False)
|
|
252
260
|
|
|
253
261
|
def zoom(self, mode, factor=1, shrink=True, expand=True):
|
|
254
|
-
for
|
|
255
|
-
|
|
262
|
+
for canvas in self.canvases.values():
|
|
263
|
+
canvas.zoom(mode, factor=factor, shrink=shrink, expand=expand)
|
|
256
264
|
|
|
257
265
|
def post_zoom_menu(self):
|
|
258
266
|
pos = Coordinates(
|
|
@@ -264,30 +272,40 @@ class ImageFrame(ttk.Frame):
|
|
|
264
272
|
def show_diff(self):
|
|
265
273
|
if self.high_contrast.get():
|
|
266
274
|
if self.comparator:
|
|
267
|
-
self.images['diff']
|
|
268
|
-
self.
|
|
275
|
+
self.images['diff'] = self.comparator.high_contrast_diff
|
|
276
|
+
self.canvases['diff'].scaled_image = None
|
|
277
|
+
self.canvases['diff'].swap_image(self.images['diff'])
|
|
269
278
|
else:
|
|
270
279
|
if self.comparator:
|
|
271
|
-
self.images['diff']
|
|
272
|
-
self.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
self.
|
|
277
|
-
|
|
278
|
-
self.images['right'].load_image(self.comparator.right)
|
|
279
|
-
self.show_diff()
|
|
280
|
+
self.images['diff'] = self.comparator.diff
|
|
281
|
+
self.canvases['diff'].scaled_image = None
|
|
282
|
+
self.canvases['diff'].swap_image(self.images['diff'])
|
|
283
|
+
# In mode 1, if diff is currently shown on the left canvas, update it
|
|
284
|
+
n = self.image_layout_style.get()
|
|
285
|
+
if n == 1 and self.current_top_image == 'diff':
|
|
286
|
+
self.canvases['left'].swap_image(self.images[self.current_top_image])
|
|
280
287
|
|
|
281
288
|
@separate_thread
|
|
282
289
|
def load_comparator(self, comparator):
|
|
283
290
|
self.comparator = comparator
|
|
284
|
-
self.images['left']
|
|
285
|
-
self.images['right']
|
|
291
|
+
self.images['left'] = self.comparator.left
|
|
292
|
+
self.images['right'] = self.comparator.right
|
|
286
293
|
self.show_diff()
|
|
294
|
+
# In modes 1 and 2, update the displayed image on the left canvas
|
|
295
|
+
n = self.image_layout_style.get()
|
|
296
|
+
if n in (1, 2):
|
|
297
|
+
self.canvases['left'].swap_image(self.images[self.current_top_image])
|
|
298
|
+
else:
|
|
299
|
+
for loc in ('left', 'right', 'diff'):
|
|
300
|
+
self.canvases[loc].swap_image(self.images[loc])
|
|
301
|
+
|
|
302
|
+
def load_images(self, left, right):
|
|
303
|
+
comparator = ImageComparator(left, right)
|
|
304
|
+
self.load_comparator(comparator)
|
|
287
305
|
|
|
288
306
|
def clear(self):
|
|
289
|
-
for
|
|
290
|
-
self.after_idle(
|
|
307
|
+
for canvas in self.canvases.values():
|
|
308
|
+
self.after_idle(canvas.clear)
|
|
291
309
|
|
|
292
310
|
def delete(self):
|
|
293
311
|
if self.comparator:
|
|
@@ -311,8 +329,20 @@ class ImageFrame(ttk.Frame):
|
|
|
311
329
|
log.info(f'COPY: {left_file} -> {right_file}')
|
|
312
330
|
self.comparator.copy_left_to_right()
|
|
313
331
|
if self.comparator.right:
|
|
314
|
-
self.images['right']
|
|
315
|
-
self.
|
|
332
|
+
self.images['right'] = self.comparator.right
|
|
333
|
+
if self.high_contrast:
|
|
334
|
+
self.images['diff'] = self.comparator.high_contrast_diff
|
|
335
|
+
else:
|
|
336
|
+
self.images['diff'] = self.comparator.diff
|
|
337
|
+
self.canvases['right'].swap_image(self.images['right'])
|
|
338
|
+
self.canvases['diff'].swap_image(self.images['diff'])
|
|
339
|
+
# In modes 1 and 2, update the displayed image on the left canvas
|
|
340
|
+
n = self.image_layout_style.get()
|
|
341
|
+
if n in (1, 2) and self.current_top_image in ('right', 'diff'):
|
|
342
|
+
self.canvases['left'].swap_image(self.images[self.current_top_image])
|
|
343
|
+
else:
|
|
344
|
+
for loc in ('right', 'diff'):
|
|
345
|
+
self.canvases[loc].swap_image(self.images[loc])
|
|
316
346
|
if self.update_item_command:
|
|
317
347
|
self.update_item_command(right_file, self.comparator)
|
|
318
348
|
|
|
@@ -325,7 +355,18 @@ class ImageFrame(ttk.Frame):
|
|
|
325
355
|
log.info(f'COPY: {right_file} -> {left_file}')
|
|
326
356
|
self.comparator.copy_right_to_left()
|
|
327
357
|
if self.comparator.left:
|
|
328
|
-
self.images['left']
|
|
329
|
-
self.
|
|
358
|
+
self.images['left'] = self.comparator.left
|
|
359
|
+
if self.high_contrast:
|
|
360
|
+
self.images['diff'] = self.comparator.high_contrast_diff
|
|
361
|
+
else:
|
|
362
|
+
self.images['diff'] = self.comparator.diff
|
|
363
|
+
self.canvases['diff'].load_image(self.comparator.diff)
|
|
364
|
+
# In modes 1 and 2, update the displayed image on the left canvas
|
|
365
|
+
n = self.image_layout_style.get()
|
|
366
|
+
if n in (1, 2) and self.current_top_image in ('left', 'diff'):
|
|
367
|
+
self.canvases['left'].swap_image(self.images[self.current_top_image])
|
|
368
|
+
else:
|
|
369
|
+
for loc in ('left', 'diff'):
|
|
370
|
+
self.canvases[loc].swap_image(self.images[loc])
|
|
330
371
|
if self.update_item_command:
|
|
331
372
|
self.update_item_command(left_file, self.comparator)
|
|
@@ -62,8 +62,8 @@ class ZoomMenu(TransientMenu):
|
|
|
62
62
|
)
|
|
63
63
|
|
|
64
64
|
def post(self, pos):
|
|
65
|
-
scaling = self.parent.
|
|
66
|
-
scaling_percent = self.parent.
|
|
65
|
+
scaling = self.parent.canvases['left'].scaling
|
|
66
|
+
scaling_percent = self.parent.canvases['left'].scaling_percent
|
|
67
67
|
self.mode_var.set('FitIfLarger')
|
|
68
68
|
if scaling.mode == ImageScaling.Mode.Original:
|
|
69
69
|
self.mode_var.set('Original')
|
|
@@ -112,4 +112,4 @@ class ZoomMenu(TransientMenu):
|
|
|
112
112
|
self.mode_var.set('Scaled')
|
|
113
113
|
|
|
114
114
|
def update_scale_var(self):
|
|
115
|
-
self.scale_var.set(self.parent.
|
|
115
|
+
self.scale_var.set(self.parent.canvases['left'].scaling_percent)
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = '0.4.
|
|
1
|
+
__version__ = '0.4.4'
|
|
2
2
|
version_info = __version__.split('.')
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|