프로젝트

일반

사용자정보

통계
| 개정판:

hytos / HYTOS / HYTOS / QtImageViewer.py @ e8f57e0a

이력 | 보기 | 이력해설 | 다운로드 (24.7 KB)

1
# coding: utf-8
2
import sys
3
import os.path
4
try:
5
    from PyQt5.QtCore import *
6
    from PyQt5.QtGui import *
7
    from PyQt5.QtWidgets import *
8
except ImportError:
9
    try:
10
        from PyQt4.QtCore import *
11
        from PyQt4.QtGui import *
12
    except ImportError:
13
        raise ImportError("ImageViewerQt: Requires PyQt5 or PyQt4.")
14
    
15
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)) + '\\Commands')
16
import DefaultCommand
17

    
18
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)) + '\\Shapes')
19
from SymbolSvgItem import SymbolSvgItem
20

    
21
__author__ = "Marcel Goldschen-Ohm <marcel.goldschen@gmail.com>"
22
__version__ = '0.9.0'
23

    
24

    
25
class QtImageViewer(QGraphicsView):
26
    """ PyQt image viewer widget for a QPixmap in a QGraphicsView scene with mouse zooming and panning.
27
    Displays a QImage or QPixmap (QImage is internally converted to a QPixmap).
28
    To display any other image format, you must first convert it to a QImage or QPixmap.
29
    Some useful image format conversion utilities:
30
        qimage2ndarray: NumPy ndarray <==> QImage    (https://github.com/hmeine/qimage2ndarray)
31
        ImageQt: PIL Image <==> QImage  (https://github.com/python-pillow/Pillow/blob/master/PIL/ImageQt.py)
32
    Mouse interaction:
33
        Left mouse button drag: Pan image.
34
        Right mouse button drag: Zoom box.
35
        Right mouse button doubleclick: Zoom to show entire image.
36
    """
37

    
38
    # Mouse button signals emit image scene (x, y) coordinates.
39
    # !!! For image (row, column) matrix indexing, row = y and column = x.
40
    leftMouseButtonPressed = pyqtSignal(float, float)
41
    rightMouseButtonPressed = pyqtSignal(float, float)
42
    leftMouseButtonMoved = pyqtSignal(float, float)
43
    rightMouseButtonMoved = pyqtSignal(float, float)
44
    leftMouseButtonReleased = pyqtSignal(float, float)
45
    rightMouseButtonReleased = pyqtSignal(float, float)
46
    leftMouseButtonDoubleClicked = pyqtSignal(float, float)
47
    rightMouseButtonDoubleClicked = pyqtSignal(float, float)
48
    #itemRemoved = pyqtSignal(QGraphicsItem)
49
    startPointChanged = pyqtSignal(float, float)
50

    
51
    '''
52
        @history    2018.06.27  Jeongwoo    Change zoom rule (Qt.KeepAspectRatioByExpanding → Qt.KeepAspectRatio)
53
    '''
54
    def __init__(self, mainWindow = None):
55
        from QtImageViewerScene import QtImageViewerScene
56

    
57
        QGraphicsView.__init__(self)
58

    
59
        self.mainWindow = mainWindow
60
        # Image is displayed as a QPixmap in a QGraphicsScene attached to this QGraphicsView.
61
        self.command = None
62
        self.scene = QtImageViewerScene(mainWindow)
63
        self.setScene(self.scene)
64
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
65

    
66
        self.scaleFactor = 1.0
67
        self.numScheduledScalings = 0
68
        self.isOriginalPointSelected = False
69

    
70
        # Store a local handle to the scene's current image pixmap.
71
        self._pixmapHandle = None
72

    
73
        # Image aspect ratio mode.
74
        # !!! ONLY applies to full image. Aspect ratio is always ignored when zooming.
75
        #   Qt.IgnoreAspectRatio: Scale image to fit viewport.
76
        #   Qt.KeepAspectRatio: Scale image to fit inside viewport, preserving aspect ratio.
77
        #   Qt.KeepAspectRatioByExpanding: Scale image to fill the viewport, preserving aspect ratio.
78
        self.aspectRatioMode = Qt.KeepAspectRatio
79

    
80
        # Scroll bar behaviour.
81
        #   Qt.ScrollBarAlwaysOff: Never shows a scroll bar.
82
        #   Qt.ScrollBarAlwaysOn: Always shows a scroll bar.
83
        #   Qt.ScrollBarAsNeeded: Shows a scroll bar only when zoomed.
84
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
85
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
86

    
87
        # Stack of QRectF zoom boxes in scene coordinates.
88
        self.zoomStack = []
89

    
90
        self.setRenderHint(QPainter.Antialiasing)
91

    
92
        self.setAcceptDrops(True)  # enable drop
93

    
94
        # Flags for enabling/disabling mouse interaction.
95
        self.canZoom = True
96
        self.canPan = True
97
        self.setMouseTracking(True)
98
        self.command = None
99
        
100
        self.guidesEnabled = False
101
        self._guidePen = QPen()
102
        self._guidePen.setColor(QColor(180, 180, 180))
103
        self._guidePen.setStyle(Qt.DashLine)
104
        self._guidePen.setWidthF(0.3)
105

    
106
        # set currentAttribute
107
        self.currentAttribute = ''
108

    
109
    '''
110
        @brief      Return Pixmap Handler
111
        @author     Jeongwoo
112
        @date       2018.06.11
113
    '''
114
    def getPixmapHandle(self):
115
        return self._pixmapHandle
116

    
117
    '''
118
        @brief      Use Default ImageViewer Command
119
        @author     Jeongwoo
120
        @date       18.04.10
121
        @history    .
122
    '''
123
    def useDefaultCommand(self):
124
        """ Use Default Command """
125
        if self.command is not None:
126
            self.command.reset()
127

    
128
        self.command = DefaultCommand.DefaultCommand(self)
129

    
130
    def hasImage(self):
131
        """ Returns whether or not the scene contains an image pixmap.
132
        """
133
        return self._pixmapHandle is not None
134

    
135
    def clearImage(self):
136
        """ Removes the current image pixmap from the scene if it exists.
137
        """
138
        if self.hasImage():
139
            self.scene.removeItem(self._pixmapHandle)
140
            self._pixmapHandle = None
141

    
142
    def pixmap(self):
143
        """ Returns the scene's current image pixmap as a QPixmap, or else None if no image exists.
144
        :rtype: QPixmap | None
145
        """
146
        if self.hasImage():
147
            return self._pixmapHandle.pixmap()
148
        return None
149

    
150
    def image(self):
151
        """ Returns the scene's current image pixmap as a QImage, or else None if no image exists.
152
        :rtype: QImage | None
153
        """
154
        if self.hasImage():
155
            return self._pixmapHandle.pixmap().toImage()
156
        return None
157

    
158
    def setImage(self, image):
159
        """ Set the scene's current image pixmap to the input QImage or QPixmap.
160
        Raises a RuntimeError if the input image has type other than QImage or QPixmap.
161
        :type image: QImage | QPixmap
162
        """
163
        try:
164
            if type(image) is QPixmap:
165
                pixmap = image
166
            elif type(image) is QImage:
167
                pixmap = QPixmap.fromImage(image)
168
            else:
169
                raise RuntimeError("ImageViewer.setImage: Argument must be a QImage or QPixmap.")
170

    
171
            self.clearImage()
172
            self.scene.clear()
173

    
174
            if self.hasImage():
175
                self._pixmapHandle.setPixmap(pixmap)
176
            else:
177
                self._pixmapHandle = self.scene.addPixmap(pixmap)
178
                self._pixmapHandle.setFlags(QGraphicsItem.ItemClipsChildrenToShape)
179

    
180
            self.setSceneRect(QRectF(pixmap.rect()))  # Set scene size to image size.
181
            self.updateViewer()
182
        except Exception as ex:
183
            from App import App
184
            from AppDocData import MessageType
185
            
186
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
187
            App.mainWnd().addMessage.emit(MessageType.Error, message)
188

    
189
    '''
190
        @brief  open a image file selected by user
191
        @author 
192
        @date
193
    '''
194
    def loadImageFromFile(self, folder='', fileName=""):
195
        """ Load an image from file.
196
        Without any arguments, loadImageFromFile() will popup a file dialog to choose the image file.
197
        With a fileName argument, loadImageFromFile(fileName) will attempt to load the specified image file directly.
198
        """
199
        try:
200
            if len(fileName) == 0:
201
                options = QFileDialog.Options()
202
                options |= QFileDialog.DontUseNativeDialog
203
                if QT_VERSION_STR[0] == '4':
204
                    fileName = QFileDialog.getOpenFileName(self, "Open image file", os.getcwd() if folder == '' else folder, "Image files(*.png *.jpg)", options=options)
205
                elif QT_VERSION_STR[0] == '5':
206
                    fileName, dummy = QFileDialog.getOpenFileName(self, "Open image file", os.getcwd() if folder == '' else folder, "Image files(*.png *.jpg)", options=options)
207
            if len(fileName) and os.path.isfile(fileName):
208
                image = QImage(fileName, format = None)
209
                self.setImage(image)
210
        except Exception as ex:
211
            from App import App
212
            from AppDocData import MessageType
213
            
214
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
215
            App.mainWnd().addMessage.emit(MessageType.Error, message)
216

    
217
        return fileName
218

    
219
    def zoom_rect(self, rect: QRectF) -> None:
220
        """zoom window within given rectangle"""
221
        self.fitInView(rect, Qt.KeepAspectRatio)
222
        if not self.zoomStack:
223
            self.zoomStack = []
224

    
225
        self.zoomStack.append(rect)
226

    
227
    def updateViewer(self, zoomNewRect=None):
228
        """Show current zoom (if showing entire image, apply current aspect ratio mode)."""
229
        if self.zoomStack:
230
            if zoomNewRect is None:
231
                self.fitInView(self.zoomStack[-1], Qt.KeepAspectRatio)  # Show zoomed rect (ignore aspect ratio).
232
            else:
233
                self.fitInView(zoomNewRect, Qt.KeepAspectRatio)
234
        else:
235
            self.zoomStack = []  # Clear the zoom stack (in case we got here because of an invalid zoom).
236
            self.fitInView(self.sceneRect(), self.aspectRatioMode)  # Show entire image (use current aspect ratio mode).
237

    
238
    def zoomImageInit(self):
239
        self.zoomStack = []
240
        self.updateViewer()
241
        self.setCursor(QCursor(Qt.ArrowCursor))
242

    
243
    '''
244
        @brief      Zoom in & out image
245
        @author     Jeongwoo
246
        @date       -
247
        @history    18.04.11    Jeongwoo    add parameter 'adjust' (@ref ResultTreeWidget.itemClickEvent(self, item, columnNo))
248
    '''
249
    def zoomImage(self, isZoomIn, event, adjust = 1):
250
        """Zoom in & out"""
251

    
252
        HALF_SIZE = 300
253
        clickPos = event.pos()
254
        left_top = self.mapToScene(clickPos.x() - HALF_SIZE//adjust, clickPos.y() - HALF_SIZE//adjust)
255
        right_bottom = self.mapToScene(clickPos.x() + HALF_SIZE//adjust, clickPos.y() + HALF_SIZE//adjust)
256
        if isZoomIn:
257
            zoomArea = QRectF(left_top, right_bottom)
258
            viewBBox = self.zoomStack[-1] if self.zoomStack else self.sceneRect()
259
            selectionBBox = zoomArea.intersected(viewBBox)
260
            self.scene.setSelectionArea(QPainterPath())  # Clear current selection area.
261
            if selectionBBox.isValid() and (selectionBBox != viewBBox):
262
                self.zoomStack.append(selectionBBox)
263
                self.updateViewer()
264
        else:
265
            zoomNewRect = None
266
            self.scene.setSelectionArea(QPainterPath())  # Clear current selection area.
267
            if self.zoomStack:
268
                zoomNewRect = self.zoomStack.pop()
269
            self.updateViewer(zoomNewRect)
270

    
271
    """
272
    def resizeEvent(self, event):
273
        Maintain current zoom on resize.
274
        self.updateViewer()
275
    """
276

    
277
    '''
278
        @brief  mouse move event
279
    '''
280
    def mouseMoveEvent(self, event):
281
        try:
282
            scenePos = self.mapToScene(event.pos())
283
            if self.command is not None:
284
                self.command.execute(['mouseMoveEvent', event, scenePos])
285
                if self.command.name == "SelectAttribute":
286
                    QGraphicsView.mouseMoveEvent(self, event)
287
                if self.command.isTreated == True: return
288
        except Exception as ex:
289
            from App import App
290
            from AppDocData import MessageType
291
            
292
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
293
            App.mainWnd().addMessage.emit(MessageType.Error, message)
294

    
295
        QGraphicsView.mouseMoveEvent(self, event)
296

    
297
    '''
298
        @brief      
299
        @author     
300
        @date       
301
        @history    block clear selection when right mouse button is clicked
302
    '''
303
    def mousePressEvent(self, event):
304
        try:
305
            if self.command is not None:
306
                scenePos = self.mapToScene(event.pos())
307
                self.command.execute(['mousePressEvent', event, scenePos])
308
                if self.command.isTreated == True: return
309
        except Exception as ex:
310
            from App import App
311
            from AppDocData import MessageType
312
            
313
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
314
            App.mainWnd().addMessage.emit(MessageType.Error, message)
315

    
316
        if event.button() != Qt.RightButton:
317
            QGraphicsView.mousePressEvent(self, event)
318

    
319
    '''
320
        @brief      
321
        @author     
322
        @date       
323
    '''
324
    def mouseReleaseEvent(self, event):
325
        try:
326
            if self.command is not None:
327
                scenePos = self.mapToScene(event.pos())
328
                instance = self.command.execute(['mouseReleaseEvent', event, scenePos])
329
                if instance is not None:
330
                    self.scene.addItem(instance)
331

    
332
                if self.command is not None and self.command.isTreated == True: 
333
                    if self.command.name == 'Default' and self.command.isCopy:
334
                        return
335
                    self.command = DefaultCommand.DefaultCommand(self)
336
                    cursor = QCursor(Qt.ArrowCursor)
337
                    QApplication.instance().setOverrideCursor(cursor)
338
                    return
339
        except Exception as ex:
340
            from App import App
341
            from AppDocData import MessageType
342
            
343
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
344
            App.mainWnd().addMessage.emit(MessageType.Error, message)
345

    
346
        QGraphicsView.mouseReleaseEvent(self, event)
347
    
348
    """
349
        @brief      Show entire image.
350
    """
351
    def mouseDoubleClickEvent(self, event):
352
        scenePos = self.mapToScene(event.pos())
353
        if self.command is not None:
354
            instance = self.command.execute(['mouseDoubleClickEvent', event, scenePos])
355
            if self.command.isTreated == True: return
356

    
357
        if event.button() == Qt.LeftButton:
358
            self.leftMouseButtonDoubleClicked.emit(scenePos.x(), scenePos.y())
359
        elif event.button() == Qt.RightButton:
360
            if self.canZoom:
361
                self.zoomStack = []  # Clear zoom stack.
362
                self.updateViewer()
363
            self.rightMouseButtonDoubleClicked.emit(scenePos.x(), scenePos.y())
364
        
365
        QGraphicsView.mouseDoubleClickEvent(self, event)
366

    
367
    '''
368
        @brief  key release event
369
        @author Jeongwoo
370
        @date   2018.??.??
371
    '''
372
    def keyReleaseEvent(self, event):
373
        if event.key() == Qt.Key_Delete:
374
            pass
375

    
376
        QGraphicsView.keyReleaseEvent(self, event)
377

    
378
    '''
379
        @brief      mouse wheel event
380
        @autor      humkyung
381
        @date       
382
    '''
383
    def wheelEvent(self, event):
384
        if event.modifiers() == Qt.ControlModifier:
385
            if self.canZoom:
386
                numDegrees = event.angleDelta() / 8
387
                if numDegrees is not None:
388
                    if numDegrees.y() > 0:
389
                        self.zoomImage(True, event)
390
                    elif numDegrees.y() < 0:
391
                        self.zoomImage(False, event)
392
        else:
393
            super().wheelEvent(event)
394

    
395
    '''
396
        @brief      
397
    '''
398
    def drawForeground(self, painter, rect):
399
        if hasattr(self, 'coords') and self.guidesEnabled:
400
            painter.setClipRect(rect)
401
            painter.setPen(self._guidePen)
402
            painter.drawLine(round(self.coords.x()), rect.top(), round(self.coords.x()), rect.bottom())
403
            painter.drawLine(rect.left(), round(self.coords.y()), rect.right(), round(self.coords.y()))
404

    
405
        '''
406
        image = self.image()
407
        if image is not None:
408
            width = rect.width()
409
            height = rect.height()
410
            if self.crosshairPos is not None:
411
                pen = QPen()
412
                pen.setColor(QColor(180, 180, 180))
413
                pen.setStyle(Qt.DashLine)
414
                pen.setWidthF(0.3)
415
                painter.setClipRect(rect)
416
                painter.setPen(pen)
417
                painter.drawLine(self.crosshairPos.x(), 0, self.crosshairPos.x(), height)#Vertical
418
                painter.drawLine(0, self.crosshairPos.y(), width, self.crosshairPos.y())#Horizontal
419
            #else:
420
            #    painter.eraseRect(QRectF(0, 0, width, height))
421
        '''
422

    
423
    '''
424
        @brief      draw background
425
        @author     humkyung
426
        @date       2018.07.23
427
    '''        
428
    def drawBackground(self, painter, rect):
429
        from HydroCalculationCommand import HydroCalculationCommand
430

    
431
        QGraphicsView.drawBackground(self, painter, rect)
432

    
433
        painter.setPen(Qt.gray)
434
        painter.setBrush(Qt.gray)
435
        rect = self.sceneRect()
436
        rect.setLeft(rect.left() + 5)
437
        rect.setTop(rect.top() + 5)
438
        painter.fillRect(rect, Qt.gray)
439

    
440
        painter.setPen(Qt.white)
441
        painter.setBrush(Qt.white)
442
        rect.setLeft(rect.left() - 5)
443
        rect.setTop(rect.top() - 5)
444
        rect.setRight(rect.right() - 5)
445
        rect.setBottom(rect.bottom() - 5)
446
        if not HydroCalculationCommand.ERRORS:
447
            painter.fillRect(rect, QBrush(Qt.white))
448
        else:
449
            painter.fillRect(rect, QBrush(Qt.lightGray))
450

    
451
    '''
452
        @history    2018.06.11  Jeongwoo    Change method to manage guideline items
453
                    humkyung 2018.08.28 remove guide lines before drawing
454
    '''
455
    GUIDELINE_ITEMS = []
456
    def showGuideline(self, pos, isShow):
457
        image = self.image()
458
        width = image.width()
459
        height = image.height()
460
        pen = QPen()
461
        pen.setColor(QColor(180, 180, 180))
462
        pen.setStyle(Qt.DashLine)
463
        pen.setWidthF(0.5)
464
        if isShow:
465
            items = self.scene.items()
466
            for item in self.GUIDELINE_ITEMS:
467
                if item in items:
468
                    self.scene.removeItem(item)
469
            self.GUIDELINE_ITEMS.clear()
470

    
471
            if pos is not None:
472
                verticalLine = self.scene.addLine(pos.x(), 0, pos.x(), height, pen)
473
                horizontalLine = self.scene.addLine(0, pos.y(), width, pos.y(), pen)
474
            else:
475
                verticalLine = self.scene.addLine(round(width*0.5), 0, round(width*0.5), height, pen)
476
                horizontalLine = self.scene.addLine(0, round(height*0.5), width, round(height*0.5), pen)
477

    
478
            self.GUIDELINE_ITEMS.append(verticalLine)
479
            self.GUIDELINE_ITEMS.append(horizontalLine)
480
        else:
481
            items = self.scene.items()
482
            for item in self.GUIDELINE_ITEMS:
483
                if item in items:
484
                    self.scene.removeItem(item)
485
            self.GUIDELINE_ITEMS.clear()
486

    
487
    '''
488
        @brief  drag enter event
489
        @author humkyung
490
        @date   2018.04.17
491
    '''
492
    def dragEnterEvent(self, event):
493
        event.acceptProposedAction()
494

    
495
    '''
496
        @brief      drag move event
497
        @author     humkyung
498
        @date       2018.04.17
499
        @history    humkyung 2018.08.21 highlight item under mouse
500
    '''
501
    def dragMoveEvent(self, event):
502
        scenePos = self.mapToScene(event.pos())
503
        items = [item for item in self.scene.items(scenePos) if type(item) is not QGraphicsPixmapItem and type(item) is not QGraphicsTextItem]
504
        if len(items) > 0:
505
            if not hasattr(self, '_underItem') or self._underItem is not items[0]:
506
                if hasattr(self, '_underItem') and self._underItem is not None:
507
                    if hasattr(self._underItem, 'highlight'):
508
                        self._underItem.highlight(False)
509

    
510
                self._underItem = items[0]
511
                if hasattr(self._underItem, 'highlight'):
512
                    self._underItem.highlight(True)
513
        #elif hasattr(self, '_underItem') and self._underItem is not None:
514
        #    self._underItem.hoverLeaveEvent(event)
515
        #    self._underItem = None
516
        
517
        event.acceptProposedAction()
518

    
519
    def dropEvent(self, event):
520
        """create a symbol"""
521
        try:
522
            from AppDocData import AppDocData
523
            import symbol
524

    
525
            scenePos = self.mapToScene(event.pos())
526
            uid = event.mimeData().text()
527
            svg = self.scene.createSymbolObject(uid, scale=None, angle=None)
528
            if svg:
529
                self.scene.place_symbol(svg, scenePos)
530

    
531
            event.acceptProposedAction()
532
        except Exception as ex:
533
            from App import App
534
            from AppDocData import MessageType
535

    
536
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
537
                                                           sys.exc_info()[-1].tb_lineno)
538
            App.mainWnd().addMessage.emit(MessageType.Error, message)
539

    
540
    '''
541
        @brief  find item by uid (SymbolSvgItem 기반, QEngineeringConnectorItem 제외, QEngineeringLineItem 포함)
542
        @author kyouho
543
        @date   2018.07.31
544
    '''
545
    def findItemByUid(self, uid):
546

    
547
        items = [item for item in self.scene.items() if hasattr(item, 'uid') and str(item.uid) == str(uid)]
548
        return items[0] if items else None
549

    
550
    def save_as_image(self, file_path):
551
        """ save scene as given file """
552

    
553
        try:
554
            rect = self.scene.itemsBoundingRect()
555
            default = QSize(rect.width()*1, rect.height()*1)
556
            image = QImage(default, QImage.Format_ARGB32_Premultiplied)
557
            painter = QPainter(image)
558

    
559
            # render the scene
560
            self.scene.render(painter, QRectF(image.rect()), rect)
561
            painter.end()
562

    
563
            # save the image to a given file
564
            image.save(file_path)
565
        except Exception as ex:
566
            from App import App
567
            from AppDocData import MessageType
568

    
569
            message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
570
                      f"{sys.exc_info()[-1].tb_lineno}"
571
            App.mainWnd().addMessage.emit(MessageType.Error, message)
572

    
573
    def save_as_svg(self, file_path):
574
        """ save scene as given file """
575
        from xml.etree.ElementTree import Element, tostring, SubElement, dump, ElementTree, parse
576

    
577
        def prettify(elem):
578
            """Return a pretty-printed XML string for the Element."""
579
            from xml.etree import ElementTree
580
            from xml.dom import minidom
581

    
582
            try:
583
                rough_string = ElementTree.tostring(elem, 'utf-8')
584
                reparsed = minidom.parseString(rough_string)
585
                return reparsed.toprettyxml(indent="  ")
586
            except Exception as ex:
587
                from AppDocData import MessageType
588

    
589
                message = f'error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:' \
590
                          f'{sys.exc_info()[-1].tb_lineno}'
591
                self.display_message.emit(MessageType.Error, message)
592

    
593
        try:
594
            rect = self.scene.itemsBoundingRect()
595

    
596
            svg = Element('svg')
597
            svg.attrib['width'] = f"{rect.width()}"
598
            svg.attrib['height'] = f"{rect.height()}"
599
            svg.attrib['viewBox'] = f"{rect.left()} {rect.top()} {rect.width()} {rect.height()}"
600
            svg.attrib['xmlns'] = 'http://www.w3.org/2000/svg'
601
            svg.attrib['xmlns:xlink'] = "http://www.w3.org/1999/xlink"
602
            svg.attrib['xmlns:inkscape'] = "http://www.inkscape.org/namespaces/inkscape"
603
            svg.attrib['version'] = "1.1"
604
            svg.attrib['baseProfile'] = "tiny"
605

    
606
            for item in self.scene.items():
607
                if hasattr(item, 'to_svg'):
608
                    node = item.to_svg(None)
609
                    if node:
610
                        svg.extend(node)
611

    
612
            with open(file_path, 'w', encoding='utf-8') as output_file:
613
                output_file.write(prettify(svg))
614
        except Exception as ex:
615
            from App import App
616
            from AppDocData import MessageType
617

    
618
            message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
619
                      f"{sys.exc_info()[-1].tb_lineno}"
620
            App.mainWnd().addMessage.emit(MessageType.Error, message)
621

    
622

    
623
if __name__ == '__main__':
624
    import sys
625
    try:
626
        from PyQt5.QtWidgets import QApplication
627
    except ImportError:
628
        try:
629
            from PyQt4.QtGui import QApplication
630
        except ImportError:
631
            raise ImportError("ImageViewerQt: Requires PyQt5 or PyQt4.")
632

    
633
    def handleLeftClick(x, y):
634
        row = int(y)
635
        column = int(x)
636

    
637
    # Create the application.
638
    app = QApplication(sys.argv)
639

    
640
    # Create image viewer and load an image file to display.
641
    viewer = QtImageViewer(None)
642
    viewer.loadImageFromFile()  # Pops up file dialog.
643

    
644
    # Handle left mouse clicks with custom slot.
645
    viewer.leftMouseButtonPressed.connect(handleLeftClick)
646

    
647
    # Show viewer and run application.
648
    viewer.show()
649
    sys.exit(app.exec_())
클립보드 이미지 추가 (최대 크기: 500 MB)