You are not logged in.
This is simple image viewer written in python3, all in one file with some additional features like cropping/rotating/flipping image, text or ellipse writing, pixel inspection and measuring mode, etc.
This is not my code; I found it somewhere in the net (don't remember where) and do some modifications to silence new python3.8 console output.
I think the license allow to place it here, so I do it, because I think the prog is nice. If you disagree with that, then you may remove this.
Enjoy it!
#!/usr/bin/python
# The MIT License (MIT)
#
# Copyright (c) 2014 Andrea Griffini
#
# 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.
import sys, gc, os, time, math, re, subprocess
try:
from PyQt5.Qt import *
pyside = False
except:
from PySide2.QtWidgets import *
from PySide2.QtGui import *
from PySide2.QtCore import *
pyside = True
try:
unicode
except:
unicode = str
QW = QWidget
def between(x, a, b, eps=0):
return min(a, b)-eps <= x <= max(a, b)+eps
def near(x, a, eps):
return a-eps <= x <= a+eps
class Viewer(QW):
def __init__(self, parent, imglist):
QW.__init__(self, parent)
bg = QImage(32, 32, QImage.Format_ARGB32)
dc = QPainter(bg)
dc.fillRect(0, 0, 32, 32, QColor(96, 96, 96))
dc.fillRect(0, 0, 16, 16, QColor(128, 128, 128))
dc.fillRect(16, 16, 16, 16, QColor(128, 128, 128))
dc = None
self.bbrush = QBrush(bg)
self.setFocusPolicy(Qt.StrongFocus)
self.setFocus()
self.imglist = imglist[:]
self.setMouseTracking(True)
self.fullscreen = False
self.titleOverlay = False
self.timer = QTimer(self)
self.timer.timeout.connect(self.checkmtime)
self.timer.start(250)
self.pos = None
self.tracking = None
self.tools = []
self.vector_overlay = []
self.load(0)
def load(self, index):
img = QImage(self.imglist[index])
if img.width() > 0:
self.vector_overlay = []
try:
#
# .vo file = vector overlay data, each line is
# <command>,<color>,<parm1>,<parm2>,...
#
# commands are:
# "L" = polyline (x1, y1, x2, y2, ...)
# "P" = filled polygon (x1, y1, x2, y2, ...)
# "C" = circle (r, cx1, cy1, cx2, cy2, ...)
# "x" = cross (x1, y1, x2, y2, ...)
# "+" = cross (x1, y1, x2, y2, ...)
#
for L in open(self.imglist[index] + ".vo"):
parts = L.strip().split(",")
if len(parts) > 2:
self.vector_overlay.append(parts[:2] + list(map(float, parts[2:])))
except:
pass
self.imgmtime = os.stat(self.imglist[index]).st_mtime
else:
self.imgmtime = None
self.imglist[:] = self.imglist[index:] + self.imglist[:index]
self.img = img
self.scaled = None
self.parent().setWindowTitle(self.mkTitle())
self.update()
gc.collect()
def checkmtime(self):
try:
imgmtime = os.stat(self.imglist[0]).st_mtime
except:
imgmtime = None
if imgmtime != self.imgmtime:
ow = self.img.width()
oh = self.img.height()
self.load(0)
if self.img.width() != ow or self.img.height() != oh:
self.zoomAll()
def resizeEvent(self, e):
self.zoomAll()
QW.resizeEvent(self, e)
def zoomAll(self):
self.scale = min(float(self.width()) / max(1, self.img.width()),
float(self.height()) / max(1, self.img.height()))
self.tx = (self.width() - self.img.width()*self.scale) / 2
self.ty = (self.height() - self.img.height()*self.scale) / 2
self.update()
def keyPressEvent(self, e):
for t in self.tools:
if t.key(e):
self.update()
return
if e.key() == Qt.Key_Right:
w, h = self.img.width(), self.img.height()
self.load(min(1, len(self.imglist) - 1))
if (w, h) != (self.img.width(), self.img.height()):
self.zoomAll()
elif e.key() == Qt.Key_Left:
w, h = self.img.width(), self.img.height()
self.load(len(self.imglist) - 1)
if (w, h) != (self.img.width(), self.img.height()):
self.zoomAll()
elif e.key() == Qt.Key_1:
self.setZoom(1)
elif e.key() == Qt.Key_2:
self.setZoom(2)
elif e.key() == Qt.Key_3:
self.setZoom(3)
elif e.key() == Qt.Key_4:
self.setZoom(4)
elif e.key() in (Qt.Key_Home, Qt.Key_0):
self.zoomAll()
elif e.key() in (Qt.Key_Escape, Qt.Key_Q):
self.parent().deleteLater()
elif e.key() == Qt.Key_U:
w, h = self.img.width(), self.img.height()
self.load(0)
if (w, h) != (self.img.width(), self.img.height()):
self.zoomAll()
elif e.key() in (Qt.Key_F, Qt.Key_Return, Qt.Key_Enter):
self.fullscreen = not self.fullscreen
if self.fullscreen:
self.parent().showFullScreen()
else:
self.parent().showNormal()
elif e.key() == Qt.Key_R:
img = QImage(self.img.height(), self.img.width(), QImage.Format_ARGB32)
img.fill(0)
dc = QPainter(img)
dc.rotate(90)
dc.drawImage(0, -self.img.height(), self.img)
dc = None
self.img = img
self.scaled = None
self.zoomAll()
gc.collect()
elif e.key() == Qt.Key_H:
self.img = self.img.mirrored(True, False)
self.scaled = None
self.update()
gc.collect()
elif e.key() == Qt.Key_V:
self.img = self.img.mirrored(False, True)
self.scaled = None
self.update()
gc.collect()
elif e.key() == Qt.Key_C:
self.tools = [CropTool(self)]
elif e.key() == Qt.Key_E:
self.tools = [EllipseTool(self)]
elif e.key() == Qt.Key_P:
self.tools = [PixColorTool(self)]
elif e.key() == Qt.Key_A:
self.tools = [ArrowTool(self)]
elif e.key() == Qt.Key_M:
self.tools = [MeasuringTool(self)]
elif e.key() == Qt.Key_Y:
self.to32bpp()
b = self.img.bits()
if not pyside:
b.setsize(self.img.height() * self.img.bytesPerLine())
b.setwriteable(True)
for i in range(0, self.img.height()*self.img.bytesPerLine(), 4):
b[i] = b[i+2] = b[i+1]
self.scaled = None
elif e.key() == Qt.Key_T:
self.tools = [TextTool(self)]
elif e.key() == Qt.Key_S:
fname = str(QFileDialog.getSaveFileName(self, "Save image as", self.imglist[0])[0])
if fname != "":
supported_extensions = [x.data().decode() for x in QImageWriter.supportedImageFormats()]
if re.match("^.*\\.(" + "|".join(supported_extensions) + ")$", fname, re.IGNORECASE):
if self.img.save(fname):
if fname not in self.imglist:
self.imglist.insert(0, fname)
self.load(0)
else:
QMessageBox.critical(self, "IView: Error saving image file",
"There was a problem saving the image file.")
elif fname[-4:].lower() == ".pgm":
# Hand-made PGM writer
try:
self.to32bpp()
data = self.img.bits().asstring(self.img.height() * self.img.bytesPerLine())
f = open(fname, "wb")
f.write(("P5\n%i %i 255\n" % (self.img.width(), self.img.height()))
.encode("ascii"))
for y in range(self.img.height()):
f.write(data[y*self.img.width()*4+1:(y+1)*self.img.width()*4+1:4])
f.close()
if fname not in self.imglist:
self.imglist.insert(0, fname)
self.load(0)
except Exception:
QMessageBox.critical(self, "IView: Error saving image file",
"There was a problem saving the image file.")
else:
QMessageBox.critical(self, "IView: Unable to save image file",
"The requested image format is not supported, supported extensions are "+
", ".join(supported_extensions))
elif e.key() == Qt.Key_X:
subprocess.Popen([os.getenv("PIXEDITOR") or "gimp", self.imglist[0]])
elif e.key() == Qt.Key_Delete:
if QMessageBox.question(self,
"Image deletion",
"Are you sure you want to delete this image?",
QMessageBox.Yes|QMessageBox.No) == QMessageBox.Yes:
try:
os.unlink(self.imglist[0])
except OSError:
QMessageBox.information(self, "IView",
"Error deleting image file")
return
self.scaled = None
self.imglist.pop(0)
if len(self.imglist) == 0:
sys.exit(0)
self.load(0)
self.zoomAll()
elif e.key() == Qt.Key_F2:
name = self.imglist[0]
d = QDialog(self)
d.setWindowTitle("Rename image")
vb = QVBoxLayout(d)
l = QLabel("New name"); vb.addWidget(l)
sle = QLineEdit(d); vb.addWidget(sle)
hb = QHBoxLayout(); vb.addLayout(hb);
hb.addWidget(QWidget(d))
ok = QPushButton("OK", d); hb.addWidget(ok)
ok.setFixedWidth(80)
cancel = QPushButton("Cancel", d); hb.addWidget(cancel)
cancel.setFixedWidth(80)
hb.addWidget(QWidget(d))
sle.setText(name)
basename_start = 1+name.rindex("/") if "/" in name else 0
if "." in name[basename_start:]:
sle.setSelection(basename_start, name[basename_start:].rindex("."))
sle.setFocus()
width = sle.fontMetrics().boundingRect(name).width()
sle.setMinimumWidth(width*9//8)
cancel.clicked.connect(d.reject)
ok.clicked.connect(d.accept)
if d.exec_() == QDialog.Accepted:
try:
s = sle.text()
os.rename(self.imglist[0], unicode(s))
self.imglist[0] = unicode(s)
self.load(0)
except Exception:
QMessageBox.critical(self, "IView: Unable to rename the file",
"There was a problem renaming the file")
elif e.key() == Qt.Key_O:
self.titleOverlay = not self.titleOverlay
elif e.key() == Qt.Key_F1:
QMessageBox.information(self, "IView",
"<center><h1>IView</h1>A simple image viewer<br>"
"by Andrea \"6502\" Griffini</center>"
"<ul>"
" <li> <b>F1</b> This help message</li>"
" <li> <b>Mouse drag</b> Pan</li>"
" <li> <b>Mouse wheel</b> Zoom</li>"
" <li> <b>Left/Right</b> Previous/next image</li>"
" <li> <b>Home</b> Zoom fit</li>"
" <li> <b>1/2/3/4</b> Display resolution to Nx</li>"
" <li> <b>F or Enter</b> Fullscreen mode</li>"
" <li> <b>O</b> Toggle file name overlay in fullscreen mode</li>"
" <li> <b>A</b> Arrow markup mode</li>"
" <li> <b>P</b> Pixel inspection mode</li>"
" <li> <b>M</b> Measure mode</li>"
" <li> <b>E</b> Ellipse markup mode</li>"
" <li> <b>T</b> Text markup mode</li>"
" <li> <b>C</b> Cropping/yanking mode</li>"
" <li> <b>Y</b> Convert to grayscale</li>"
" <li> <b>R</b> Rotate image 90 degrees</li>"
" <li> <b>H</b> Horizontal flip</li>"
" <li> <b>V</b> Vertical flip</li>"
" <li> <b>U</b> Reload original image</li>"
" <li> <b>S</b> Save modified image</li>"
" <li> <b>F2</b> Rename current file</li>"
" <li> <b>Del</b> Delete current file</li>"
" <li> <b>X</b> Run external editor</li>"
"</ul>")
self.update()
def sizeHint(self):
return QSize(900, 700)
def setZoom(self, sf):
if self.pos:
mx, my = self.pos[0] * self.scale + self.tx, self.pos[1] * self.scale + self.ty
self.scale = sf
self.tx = mx - self.pos[0] * self.scale
self.ty = my - self.pos[1] * self.scale
else:
mx, my = self.width()/2, self.height()/2
lx, ly = (mx - self.tx)/self.scale, (my - self.ty)/self.scale
self.scale = sf
self.tx = mx - lx * self.scale
self.ty = my - ly * self.scale
self.update()
def wheelEvent(self, e):
k = 1.01 if (int(e.modifiers()) & Qt.ShiftModifier) != 0 else 1.2
self.pos = (e.x() - self.tx) / self.scale, (e.y() - self.ty) / self.scale
sf = self.scale
d = int(e.angleDelta().y() / 120.)
while d > 0:
sf *= k
d -= 1
while d < 0:
sf /= k
d += 1
if sf < 0.01:
sf = 0.01
if sf > 10000:
sf = 10000
self.setZoom(sf)
def paintEvent(self, e):
dc = QPainter(self)
w, h = self.width(), self.height()
dc.fillRect(0, 0, w, h, QBrush(QColor(64, 64, 64)))
dc.fillRect(int(self.tx+1), int(self.ty+1), int(self.img.width()*self.scale-2), int(self.img.height()*self.scale-2), self.bbrush)
if self.scale > 1:
xf = QTransform().translate(self.tx, self.ty).scale(self.scale, self.scale)
dc.drawImage(QRectF(self.rect()), self.img, xf.inverted()[0].mapRect(QRectF(self.rect())))
else:
if self.scaled is None or self.scaled[0] != self.scale:
sw = int(self.img.width() * self.scale)
sh = int(self.img.height() * self.scale)
simg = self.img.scaled(QSize(sw, sh), Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
self.scaled = [self.scale, simg]
dc.drawImage(int(self.tx), int(self.ty), self.scaled[1])
for L in self.vector_overlay:
if L[0] == 'L':
dc.setPen(QPen(QColor(L[1])))
pts = []
for i in range(2, len(L)-1, 2):
pts.append(QPointF(L[i]*self.scale + self.tx, L[i+1]*self.scale + self.ty))
dc.drawPolyline(QPolygonF(pts))
elif L[0] == 'P':
dc.setBrush(QBrush(QColor(L[1])))
dc.setPen(Qt.NoPen)
pts = []
for i in range(2, len(L)-1, 2):
pts.append(QPointF(L[i]*self.scale + self.tx, L[i+1]*self.scale + self.ty))
dc.drawPolygon(QPolygonF(pts))
elif L[0] == 'C':
dc.setBrush(QBrush())
dc.setPen(QPen(QColor(L[1])))
r = L[2] * self.scale
pts = []
for i in range(3, len(L)-1, 2):
dc.drawEllipse(QRectF(L[i]*self.scale + self.tx - r,
L[i+1]*self.scale + self.ty - r,
r*2, r*2))
elif L[0] == 'x' or L[0] == '+':
dc.setPen(QPen(QColor(L[1])))
for i in range(2, len(L)-1, 2):
x = L[i]*self.scale + self.tx
y = L[i+1]*self.scale + self.ty
if L[0] == 'x':
dc.drawLine(QPointF(x-10, y-10), QPointF(x+10, y+10))
dc.drawLine(QPointF(x+10, y-10), QPointF(x-10, y+10))
else:
dc.drawLine(QPointF(x-14, y), QPointF(x+14, y))
dc.drawLine(QPointF(x, y-14), QPointF(x, y+14))
y = 16
dc.setFont(QFont("sans-serif", 11))
if len(self.tools)==0 and self.fullscreen and self.titleOverlay:
self.statusText(dc, self.mkTitle())
for t in self.tools:
t.draw(dc)
def mkTitle(self):
if self.img.isNull():
name = self.imglist[0] + " (unable to load as image)"
else:
name = self.imglist[0] + " (%i x %i x %ibpp = %0.2f MB)" % (
self.img.width(),
self.img.height(),
self.img.depth(),
self.img.height() * self.img.bytesPerLine() / (1024.0*1024.0))
if "/" in name:
name = name[name.rindex("/")+1:]
return name
def mousePressEvent(self, e):
self.pos = ((e.x() - self.tx) / self.scale,
(e.y() - self.ty) / self.scale)
for t in self.tools:
self.tracking = t.hit(e.x(), e.y(), e.button())
if self.tracking:
break
else:
cur = [e.x(), e.y()]
def pan(mx, my):
if mx is not None:
self.tx += mx - cur[0]
self.ty += my - cur[1]
cur[0] = mx
cur[1] = my
self.tracking = pan
def mouseMoveEvent(self, e):
self.pos = ((e.x() - self.tx) / self.scale,
(e.y() - self.ty) / self.scale)
if self.tracking:
self.tracking(e.x(), e.y())
self.update()
def mouseReleaseEvent(self, e):
if self.tracking:
self.tracking(None, None)
self.tracking = None
def map(self, x, y):
return self.tx + x*self.scale, self.ty + y*self.scale
def revmap(self, x, y):
return (x - self.tx)/self.scale, (y - self.ty)/self.scale
def to32bpp(self):
if self.img.format() != QImage.Format_ARGB32:
self.img = self.img.convertToFormat(QImage.Format_ARGB32)
self.scaled = None
self.update()
def statusText(self, dc, msg):
dc.setPen(QPen(QColor(0, 0, 0)))
dc.drawText(4, 20, msg )
dc.drawText(7, 20, msg )
dc.drawText(5, 19, msg )
dc.drawText(5, 21, msg )
dc.setPen(QPen(QColor(0, 255, 0)))
dc.drawText(5, 20, msg )
class Tool(object):
pen = 4
color = 0
colors = [QColor(255, 0, 0),
QColor(0, 255, 0),
QColor(0, 0, 255)]
def __init__(self, iview):
self.iview = iview
self.font = QFont("sans-serif")
self.font.setPixelSize(self.pen * 8 + 11)
def key(self, e):
if e.key() == Qt.Key_R:
Tool.color = 0
self.iview.update()
return True
if e.key() == Qt.Key_G:
Tool.color = 1
self.iview.update()
return True
if e.key() == Qt.Key_B:
Tool.color = 2
self.iview.update()
return True
if e.key() >= Qt.Key_1 and e.key() <= Qt.Key_9:
Tool.pen = (e.key() - int(Qt.Key_1))*2
self.font.setPixelSize(self.pen * 8 + 11)
self.iview.update()
return True
if e.key() == Qt.Key_Escape:
self.iview.tools.remove(self)
return True
def text(self, dc, msg):
return self.iview.statusText(dc, msg)
class RectTool(Tool):
def __init__(self, iview):
Tool.__init__(self, iview)
self.geo = [0, 0, 0, 0]
def rect(self):
x0, y0, x1, y1 = self.geo
x0, y0 = map(int, self.iview.map(self.geo[0], self.geo[1]))
x1, y1 = map(int, self.iview.map(self.geo[2], self.geo[3]))
if x0 > x1: x0, x1 = x1, x0
if y0 > y1: y0, y1 = y1, y0
return x0, y0, x1, y1
def draw(self, dc):
x0, y0, x1, y1 = self.rect()
dc.setBrush(QBrush())
dc.setPen(QPen(QColor(0, 0, 0), 4))
dc.drawRect(QRectF(x0, y0, x1-x0, y1-y0))
dc.setPen(QPen(QColor(255, 255, 255), 2, Qt.DashLine))
dc.drawRect(QRectF(x0, y0, x1-x0, y1-y0))
dc.setPen(QPen(QColor(0, 0, 0)))
dc.setBrush(QColor(255, 0, 0))
for x, y in ((x0, y0), (x1, y0), (x0, y1), (x1, y1)):
dc.drawEllipse(QRectF(x-4, y-4, 8, 8))
def hit(self, mx, my, button):
if button == Qt.MiddleButton and app.keyboardModifiers() == Qt.ShiftModifier:
print(self.geo)
elif button == Qt.LeftButton:
x0, y0, x1, y1 = self.geo
x0, y0 = map(int, self.iview.map(self.geo[0], self.geo[1]))
x1, y1 = map(int, self.iview.map(self.geo[2], self.geo[3]))
f = 0
if near(mx, x0, 8) and between(my, y0, y1, 8): f |= 1
if near(my, y0, 8) and between(mx, x0, x1, 8): f |= 2
if near(mx, x1, 8) and between(my, y0, y1, 8): f |= 4
if near(my, y1, 8) and between(mx, x0, x1, 8): f |= 8
if f == 0 and between(mx, x0, x1) and between(my, y0, y1):
f = 15
else:
if (f & 5) == 5: f ^= 4
if (f & 10) == 10: f ^= 8
if f == 0:
xx, yy = map(int, self.iview.revmap(mx, my))
self.geo = [xx, yy, xx, yy]
f = 4+8
if f:
cur = list(map(int, self.iview.revmap(mx, my)))
def tracking(x, y):
if x is not None:
x, y = map(int, self.iview.revmap(x, y))
dx, dy = x - cur[0], y - cur[1]
cur[0], cur[1] = x, y
if f & 1: self.geo[0] += dx
if f & 2: self.geo[1] += dy
if f & 4: self.geo[2] += dx
if f & 8: self.geo[3] += dy
self.geo[0] = max(0, min(self.geo[0], self.iview.img.width()))
self.geo[1] = max(0, min(self.geo[1], self.iview.img.height()))
self.geo[2] = max(0, min(self.geo[2], self.iview.img.width()))
self.geo[3] = max(0, min(self.geo[3], self.iview.img.height()))
return tracking
class CropTool(RectTool):
def __init__(self, iview):
RectTool.__init__(self, iview)
def draw(self, dc):
x0, y0, x1, y1 = self.rect()
dark = QBrush(QColor(0, 0, 0, 128))
w, h = self.iview.width(), self.iview.height()
if y0 > 0: dc.fillRect(0, 0, w, y0, dark)
if x0 > 0: dc.fillRect(0, y0, x0, y1-y0, dark)
if x1 < w: dc.fillRect(x1, y0, w-x1, y1-y0, dark)
if y1 < h: dc.fillRect(0, y1, w, h-y1, dark)
RectTool.draw(self, dc)
self.text(dc, "Crop: (%i, %i) - (%i, %i) = %i x %i (A to select all, Y to copy)" %
(self.geo[0], self.geo[1],
self.geo[2], self.geo[3],
abs(self.geo[2]-self.geo[0]),
abs(self.geo[3]-self.geo[1])))
def key(self, e):
if e.key() in (Qt.Key_Return, Qt.Key_C, Qt.Key_Enter, Qt.Key_Y):
x0, y0, x1, y1 = self.geo
if x0 > x1: x0, x1 = x1, x0
if y0 > y1: y0, y1 = y1, y0
x0, y0 = max(0, x0), max(0, y0)
x1, y1 = min(self.iview.img.width(), x1), min(self.iview.img.height(), y1)
if x0 < x1 and y0 < y1:
cropped = self.iview.img.copy(x0, y0, x1-x0, y1-y0)
if e.key() == Qt.Key_Y:
QApplication.clipboard().setImage(cropped)
else:
self.iview.img = cropped
self.iview.scaled = None
self.iview.zoomAll()
self.iview.tools.remove(self)
return True
elif e.key() in (Qt.Key_A, ):
self.geo = [0, 0, self.iview.img.width(), self.iview.img.height()]
return True
else:
return Tool.key(self, e)
class EllipseTool(RectTool):
def __init__(self, iview):
RectTool.__init__(self, iview)
def draw(self, dc):
x0, y0, x1, y1 = self.rect()
dc.setPen(QPen(self.colors[self.color], self.pen*self.iview.scale))
dc.setBrush(QBrush())
dc.drawEllipse(x0, y0, x1-x0, y1-y0)
dc.setPen(QPen(QColor(0, 0, 0)))
dc.setBrush(QColor(255, 0, 0))
for x, y in (((x0+x1)/2, y0), ((x0+x1)/2, y1),
(x0, (y0+y1)/2), (x1, (y0+y1)/2),
(x0, y0), (x1, y0), (x0, y1), (x1, y1)):
dc.drawEllipse(QRectF(x-4, y-4, 8, 8))
self.text(dc, "Ellipse drawing mode: 1-9=thickness, r/g/b=color")
def key(self, e):
if e.key() in (Qt.Key_Return, Qt.Key_E, Qt.Key_Enter):
x0, y0, x1, y1 = self.geo
if x0 > x1: x0, x1 = x1, x0
if y0 > y1: y0, y1 = y1, y0
x0, y0 = max(0, x0), max(0, y0)
x1, y1 = min(self.iview.img.width(), x1), min(self.iview.img.height(), y1)
if x0 < x1 and y0 < y1:
self.iview.to32bpp()
dc = QPainter(self.iview.img)
dc.setPen(QPen(self.colors[self.color], self.pen))
dc.setBrush(QBrush())
dc.drawEllipse(x0, y0, x1-x0, y1-y0)
dc = None
self.iview.scaled = None
self.iview.tools.remove(self)
return True
else:
return Tool.key(self, e)
class PixColorTool(Tool):
def __init__(self, iview):
Tool.__init__(self, iview)
self.p = None
def draw(self, dc):
if self.p is None:
self.text(dc, "Pixel inspection; click for color/position")
else:
x, y = self.p
col = self.iview.img.pixel(x, y)
a = (col >> 24) & 255
r = (col >> 16) & 255
g = (col >> 8) & 255
b = (col >> 0) & 255
c0 = QBrush(QColor(0, 0, 0))
c1 = QBrush(QColor(255, 255, 255))
x0, y0 = self.iview.map(x, y)
x1, y1 = self.iview.map(x+1, y+1)
h = (y1 - y0) * 10
s = (y1 - y0) * 2
dc.fillRect(int(x0-1), int(y0-h-s-1), int(x1-x0+2), int(h+2), c0)
dc.fillRect(int(x0-1), int(y1+s-1), int(x1-x0+2), int(h+2), c0)
dc.fillRect(int(x0-h-s-1), int(y0-1), int(h+2), int(y1-y0+2), c0)
dc.fillRect(int(x1+s-1), int(y0-1), int(h+2), int(y1-y0+2), c0)
dc.fillRect(int(x0), int(y0-h-s), int(x1-x0), int(h), c1)
dc.fillRect(int(x0), int(y1+s), int(x1-x0), int(h), c1)
dc.fillRect(int(x0-h-s), int(y0), int(h), int(y1-y0), c1)
dc.fillRect(int(x1+s), int(y0), int(h), int(y1-y0), c1)
self.text(dc, "Pixel(x=%i, y=%i): rgb(%i, %i, %i), #%02x%02x%02x, alpha=%i" % (x, y, r, g, b, r, g, b, a))
def hit(self, mx, my, button):
if button == Qt.LeftButton:
def proc(x, y):
if 0 <= x < self.iview.img.width() and 0 <= y < self.iview.img.height():
self.p = (x, y)
self.iview.update()
proc(*map(int, self.iview.revmap(mx, my)))
def tracking(x, y):
if x is not None:
proc(*map(int, self.iview.revmap(x, y)))
return tracking
class ArrowTool(Tool):
k = 8
def __init__(self, iview):
Tool.__init__(self, iview)
self.pts = [0, 0, 0, 0]
def draw(self, dc):
x0, y0 = map(int, self.iview.map(*self.pts[:2]))
x1, y1 = map(int, self.iview.map(*self.pts[2:]))
dx, dy = (x1 - x0)//self.k, (y1 - y0)//self.k
nx, ny = (y1 - y0)//self.k//2, (x0 - x1)//self.k//2
dc.setPen(QPen(self.colors[self.color], self.pen*self.iview.scale,
Qt.SolidLine, Qt.RoundCap))
dc.setBrush(QBrush())
dc.drawLine(x0, y0, x1, y1)
dc.drawLine(x1-dx-nx, y1-dy-ny, x1, y1)
dc.drawLine(x1-dx+nx, y1-dy+ny, x1, y1)
self.text(dc, "Arrow drawing mode, 1-9=thickness, r/g/b=color, +/- changes head")
def hit(self, mx, my, button):
if button == Qt.LeftButton:
x0, y0 = map(int, self.iview.map(*self.pts[:2]))
x1, y1 = map(int, self.iview.map(*self.pts[2:]))
i = -1
if (mx - x0)**2 + (my - y0)**2 < 32**2:
i = 0
elif (mx - x1)**2 + (my - y1)**2 < 32**2:
i = 2
else:
xx, yy = self.iview.revmap(mx, my)
self.pts = [xx, yy, xx, yy]
i = 2
def tracking(x, y):
if x is not None:
self.pts[i:i+2] = list(map(int, self.iview.revmap(x, y)))
return tracking
def key(self, e):
if e.key() == Qt.Key_Minus:
if ArrowTool.k < 100: ArrowTool.k += 1
if e.key() == Qt.Key_Plus:
if ArrowTool.k > 4: ArrowTool.k -= 1
if e.key() in (Qt.Key_Return, Qt.Key_A, Qt.Key_Enter):
self.iview.to32bpp()
x0, y0, x1, y1 = map(int, self.pts)
dx, dy = (x1 - x0)//self.k, (y1 - y0)//self.k
nx, ny = (y1 - y0)//self.k//2, (x0 - x1)//self.k//2
dc = QPainter(self.iview.img)
dc.setPen(QPen(self.colors[self.color], self.pen,
Qt.SolidLine, Qt.RoundCap))
dc.setBrush(QBrush())
dc.drawLine(x0, y0, x1, y1)
dc.drawLine(x1-dx-nx, y1-dy-ny, x1, y1)
dc.drawLine(x1-dx+nx, y1-dy+ny, x1, y1)
dc = None
self.iview.scaled = None
self.iview.tools.remove(self)
return True
return Tool.key(self, e)
class MeasuringTool(Tool):
k = 8
def __init__(self, iview):
Tool.__init__(self, iview)
self.pts = [0, 0, 0, 0]
def draw(self, dc):
x0, y0 = map(int, self.iview.map(*self.pts[:2]))
x1, y1 = map(int, self.iview.map(*self.pts[2:]))
dx, dy = (x1 - x0)//self.k, (y1 - y0)//self.k
nx, ny = (y1 - y0)//self.k//2, (x0 - x1)//self.k//2
dc.setPen(QPen(self.colors[self.color]))
dc.setBrush(QBrush())
dc.drawLine(x0, y0, x1, y1)
dc.drawLine(x1-dx-nx, y1-dy-ny, x1, y1)
dc.drawLine(x1-dx+nx, y1-dy+ny, x1, y1)
dc.drawLine(x0+dx-nx, y0+dy-ny, x0, y0)
dc.drawLine(x0+dx+nx, y0+dy+ny, x0, y0)
dc.drawLine(x0+nx, y0+ny, x0-nx, y0-ny)
dc.drawLine(x1+nx, y1+ny, x1-nx, y1-ny)
x0, y0, x1, y1 = self.pts
dx, dy = x1 - x0, y1 - y0
L = (dx**2 + dy**2)**0.5
angle = 0 if L == 0 else math.atan2(dy, dx)*180/math.pi
self.text(dc, "Measuring mode: (%i,%i)-(%i,%i) : %ix%i = %0.3fpx, %0.3f degrees" % (
x0, y0, x1, y1, dx, dy, L, angle))
def hit(self, mx, my, button):
if button == Qt.LeftButton:
x0, y0 = map(int, self.iview.map(*self.pts[:2]))
x1, y1 = map(int, self.iview.map(*self.pts[2:]))
i = -1
if (mx - x0)**2 + (my - y0)**2 < 8**2:
i = 0
elif (mx - x1)**2 + (my - y1)**2 < 8**2:
i = 2
else:
xx, yy = map(int, self.iview.revmap(mx, my))
self.pts = [xx, yy, xx, yy]
i = 2
def tracking(x, y):
if x is not None:
self.pts[i:i+2] = list(map(int, self.iview.revmap(x, y)))
if app.keyboardModifiers() == Qt.ShiftModifier:
if (abs(self.pts[i] - self.pts[0+2-i]) <
abs(self.pts[i+1] - self.pts[1+3-(i+1)])):
self.pts[i] = self.pts[0+2-i]
else:
self.pts[i+1] = self.pts[1+3-(i+1)]
return tracking
def key(self, e):
if e.key() == Qt.Key_Minus:
if MeasuringTool.k < 100: MeasuringTool.k += 1
if e.key() == Qt.Key_Plus:
if MeasuringTool.k > 4: MeasuringTool.k -= 1
return Tool.key(self, e)
class TextTool(Tool):
def __init__(self, iview):
Tool.__init__(self, iview)
self.text = ""
x0, y0 = map(int, self.iview.revmap(0, 0))
x1, y1 = map(int, self.iview.revmap(self.iview.width(), self.iview.height()))
self.pos = [(x0 + x1)//2, (y0 + y1)//2]
self.blink = int(time.time() * 2) + 1
QTimer.singleShot(250, lambda : self.iview.update())
def draw(self, dc):
x, y = self.pos
dc.save()
dc.translate(self.iview.tx, self.iview.ty)
dc.scale(self.iview.scale, self.iview.scale)
dc.setFont(self.font)
dc.setPen(QPen(self.colors[self.color]))
caret = (int(time.time() * 2) - self.blink) & 1
dc.drawText(x, y, self.text + "|" if caret else self.text)
dc.restore()
Tool.text(self, dc, "Text tool: ctrl-1...9=Font size, ctrl-R/G/B color, ENTER=Accept")
QTimer.singleShot(250, lambda : self.iview.update())
def hit(self, mx, my, button):
if button == Qt.LeftButton:
cur = list(map(int, self.iview.revmap(mx, my)))
def tracking(mx, my):
if mx is not None:
x, y = map(int, self.iview.revmap(mx, my))
self.pos[0] += x - cur[0]
self.pos[1] += y - cur[1]
cur[0] = x
cur[1] = y
return tracking
def key(self, e):
self.blink = int(time.time() * 2) + 1
if e.key() == Qt.Key_Backspace:
self.text = self.text[:-1]
return True
elif e.key() == Qt.Key_Escape:
return Tool.key(self, e)
elif e.key() in (Qt.Key_Return, Qt.Key_Enter):
self.iview.to32bpp()
x, y = self.pos
dc = QPainter(self.iview.img)
dc.setFont(self.font)
dc.setPen(QPen(self.colors[self.color]))
dc.drawText(x, y, self.text)
dc = None
self.iview.scaled = None
self.iview.tools.remove(self)
return True
elif e.text() and e.modifiers() != Qt.ControlModifier:
self.text += unicode(e.text())
return True
return Tool.key(self, e)
class MyDialog(QDialog):
def __init__(self, parent, imglist):
QDialog.__init__(self, parent, Qt.Window)
self.ws = Viewer(self, imglist)
L = QVBoxLayout(self)
L.setContentsMargins(0, 0, 0, 0)
L.addWidget(self.ws)
self.setModal(True)
self.show()
uargv = sys.argv
if len(uargv) == 1:
print("Missing filename")
sys.exit(1)
app = QApplication(sys.argv)
# override the excepthook to actually quit on Ctrl-C
def ctrlc_excepthook(type, value, tback):
if isinstance(value, KeyboardInterrupt):
# if we got a Ctrl-C try to quit gracefully
app.quit()
else:
# otherwise call the default hook
sys.__excepthook__(type, value, tback)
sys.excepthook = ctrlc_excepthook
if sys.version_info[0]<3:
# if we are on Python 2, let Qt deal with the encoding madness
# (on Python 3 sys.argv is already a list of unicode strings)
uargv = [unicode(x) for x in app.arguments()]
if len(uargv) in (2, 3) and uargv[1] == "--screenshot":
screen = QGuiApplication.primaryScreen()
img = screen.grabWindow(0);
fname = "iview-screenshot.png" if len(uargv) == 2 else uargv[2]
img.save(fname)
uargv[1:] = [fname]
class SortWrapper:
def __init__(self, x):
self.x = x
def __lt__(self, other):
if type(self.x) is type(other.x):
return self.x < other.x
else:
return id(type(self.x)) < id(type(other.x))
def __eq__(self, other):
return self.x == other.x
def smartSort(s):
return tuple(SortWrapper(int(x)) if x[0] in "0123456789" else SortWrapper(x)
for x in re.findall("[0-9]+|[^0-9]+", s.upper()))
filelist = uargv[1:]
if len(filelist) == 1 and filelist[0][:8] == "file:///":
filelist[0] = filelist[0][7:]
if len(filelist) == 1:
slash = filelist[0].rindex("/") if "/" in filelist[0] else -1
fl = []
supported_extensions = [x.data().decode() for x in QImageReader.supportedImageFormats()]
fre = re.compile("^.*\\.(" + "|".join(supported_extensions) + ")$", re.IGNORECASE)
for f in os.listdir(filelist[0][:slash+1] + "."):
if fre.match(f):
fl.append(filelist[0][:slash+1] + f)
fl.sort(key = smartSort)
if filelist[0] in fl:
i = fl.index(filelist[0])
filelist = fl if i == 0 else fl[i:] + fl[:i]
aa = MyDialog(None, filelist)
aa.exec_()
aa = None
Last edited by xerxes_ (2020-03-24 12:38:38)
Offline