Example #1
0
def to_qimage(im, color_table=grey, copy=True):
    if color_table is not None:
        im = to_gray(im)

    if len(im.shape) == 3:
        if im.shape[2] == 3:
            qim = QImage(im.data, im.shape[1], im.shape[0], im.strides[0], QImage.Format_RGB888)
        elif im.shape[2] == 4:
            qim = QImage(im.data, im.shape[1], im.shape[0], im.strides[0], QImage.Format_ARGB32)
        elif im.shape[2] == 1:
            qim = QImage(im.data, im.shape[1], im.shape[0], im.strides[0], QImage.Format_Indexed8)
            qim.setColorTable(color_table)

    elif len(im.shape) == 2:
        if im.dtype == numpy.uint8:
            qim = QImage(im.data, im.shape[1], im.shape[0], im.strides[0], QImage.Format_Indexed8)
            if color_table:
                qim.setColorTable(color_table)
        elif im.dtype == numpy.uint16:
            im = (im/16)
            im = numpy.require(im, dtype=numpy.uint8, requirements='C')
            qim = QImage(im.data, im.shape[1], im.shape[0], im.strides[0], QImage.Format_Indexed8)
            if color_table:
                qim.setColorTable(color_table)
        else:
            raise TypeError("Unsupported data type")

    else:
        raise TypeError("Wrong image shape {}".format(im.shape))

    return qim.copy() if copy else qim
Example #2
0
    def capture(
        self,
        region=None,
        selector=None,
        format=None,
    ):
        """Returns snapshot as QImage.

        :param region: An optional tuple containing region as pixel
            coodinates.
        :param selector: A selector targeted the element to crop on.
        :param format: The output image format.
        """

        if format is None:
            format = QImage.Format_ARGB32_Premultiplied

        self.main_frame.setScrollBarPolicy(
            Qt.Vertical,
            Qt.ScrollBarAlwaysOff,
        )
        self.main_frame.setScrollBarPolicy(
            Qt.Horizontal,
            Qt.ScrollBarAlwaysOff,
        )
        frame_size = self.main_frame.contentsSize()
        max_size = 23170 * 23170
        if frame_size.height() * frame_size.width() > max_size:
            self.logger.warning("Frame size is too large.")
            default_size = self.page.viewportSize()
            if default_size.height() * default_size.width() > max_size:
                return None
        else:
            self.page.setViewportSize(self.main_frame.contentsSize())

        self.logger.info("Frame size -> %s", str(self.page.viewportSize()))

        image = QImage(self.page.viewportSize(), format)
        painter = QPainter(image)

        if region is None and selector is not None:
            region = self.region_for_selector(selector)

        if region:
            x1, y1, x2, y2 = region
            w, h = (x2 - x1), (y2 - y1)
            reg = QRegion(x1, y1, w, h)
            self.main_frame.render(painter, reg)
        else:
            self.main_frame.render(painter)

        painter.end()

        if region:
            x1, y1, x2, y2 = region
            w, h = (x2 - x1), (y2 - y1)
            image = image.copy(x1, y1, w, h)

        return image
Example #3
0
def to_QImage(im, copy=False):
    if im is None:
        return QImage()

    if im.dtype == np.uint8:
        if len(im.shape) == 2:
            qim = QImage(im.data, im.shape[1], im.shape[0], im.strides[0], QImage.Format_Indexed8)
            qim.setColorTable(gray_color_table)
            return qim.copy() if copy else qim

        elif len(im.shape) == 3:
            if im.shape[2] == 3:
                qim = QImage(im.data, im.shape[1], im.shape[0], im.strides[0], QImage.Format_RGB888);
                return qim.copy() if copy else qim
            elif im.shape[2] == 4:
                qim = QImage(im.data, im.shape[1], im.shape[0], im.strides[0], QImage.Format_ARGB32);
                return qim.copy() if copy else qim

    raise NotImplementedException
Example #4
0
def toQImage(im, copy=False):
    gray_color_table = [qRgb(i, i, i) for i in range(256)]
    if im is None:
        return QImage()
    if im.dtype == np.uint8:
        if len(im.shape) == 2:
            qim = QImage(im.data, im.shape[1], im.shape[0], im.strides[0],
                         QImage.Format_Indexed8)
            qim.setColorTable(gray_color_table)
            return qim.copy() if copy else qim

        elif len(im.shape) == 3:
            if im.shape[2] == 3:
                qim = QImage(im.data, im.shape[1], im.shape[0], im.strides[0],
                             QImage.Format_RGB888)
                return qim.copy() if copy else qim
            elif im.shape[2] == 4:
                qim = QImage(im.data, im.shape[1], im.shape[0], im.strides[0],
                             QImage.Format_ARGB32)
                return qim.copy() if copy else qim
Example #5
0
    def __init__(self, title: str, cover: qtg.QImage):
        super().__init__()

        cover = cover.scaledToHeight(300, qtc.Qt.TransformationMode.SmoothTransformation)
        cover = cover.copy(self._rect)
        icon = qtg.QIcon(qtg.QPixmap.fromImage(cover))
        self.setToolButtonStyle(qtc.Qt.ToolButtonIconOnly)
        self.setIcon(icon)
        self.setIconSize(qtc.QSize(300, 300))
        self.setToolTip(title)
        self.setToolTipDuration(0)
Example #6
0
    def __init__(self, object_set: int, palette_index: int):
        png = QImage(str(data_dir.joinpath("gfx.png")))

        png.convertTo(QImage.Format_RGB888)

        rows_per_object_set = 256 // 64

        y_offset = 12 * rows_per_object_set * Block.HEIGHT

        self.png_data = png.copy(QRect(0, y_offset, png.width(), png.height() - y_offset))

        self.palette_group = load_palette_group(object_set, palette_index)
Example #7
0
    def _draw_mario(self, painter: QPainter, level: Level):
        mario_actions = QImage(str(data_dir / "mario.png"))

        mario_actions.convertTo(QImage.Format_RGBA8888)

        mario_position = QPoint(*level.header.mario_position()) * self.block_length

        x_offset = 32 * level.start_action

        mario_cutout = mario_actions.copy(QRect(x_offset, 0, 32, 32)).scaled(
            2 * self.block_length, 2 * self.block_length
        )

        painter.drawImage(mario_position, mario_cutout)
Example #8
0
    def _writeFrame(self, surface: QImage):
        w = self.stream.width
        h = self.stream.height
        surface = surface.scaled(w, h) if self.scale else surface.copy()

        # Draw the mouse pointer. Render mouse clicks?
        p = QPainter(surface)
        p.setBrush(QColor.fromRgb(255, 255, 0, 180))
        (x, y) = self.mouse
        p.drawEllipse(x, y, 5, 5)
        p.end()

        # Output frame.
        frame = av.VideoFrame.from_image(ImageQt.fromqimage(surface))
        for packet in self.stream.encode(frame):
            if self.progress:
                self.progress()
            self.mp4.mux(packet)
Example #9
0
class QMoon(QWidget):
    """Small widget displays today's moon."""
    def __init__(self,
                 pos=(0, 0),
                 parent=None,
                 size=216,
                 web=False,
                 save=False,
                 debug=0):
        super(QMoon, self).__init__(parent)
        self.total_images = 8760
        self.moon_domain = "https://svs.gsfc.nasa.gov"
        self.moon_path_2021 = "/vis/a000000/a004800/a004874/"
        # https://svs.gsfc.nasa.gov/vis/a000000/a004900/a004955/frames/216x216_1x1_30p/moon.8597.jpg
        self.moon_path = "/vis/a000000/a004900/a004955/"
        self.debug = debug
        self.size = size
        self.get_from_web = web
        self.save = save
        # self.setGeometry(pos[0], pos[1], self.size, self.size)
        self.moon = QLabel(self)
        self.moon.setGeometry(pos[0], pos[1], self.size, self.size)
        self.update()

        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update)
        self.timer.start(3600 * 1000)
        self.image = None
        self.pixmap = None
        self.moon_image_number = 1

    @Slot()
    def update(self):
        if self.debug > 0:
            print("Updating the Moon Phase pixmap.")
        self.pixmap = self.get_moon_image()
        self.moon.setPixmap(self.pixmap)

    def get_moon_image_number(self):
        #
        # Conversion from the jscript.
        #
        now = datetime.utcnow()
        janone = datetime(now.year, 1, 1, 0, 0, 0)
        self.moon_image_number = round(
            (datetime.utcnow() - janone).total_seconds() / 3600)
        return self.moon_image_number <= self.total_images

    def get_moon_image(self):

        if not self.get_moon_image_number():
            print("Could not get the moon. Are we in a new year?")

        if self.debug:
            print(f"We are using moon image number: {self.moon_image_number}")
        if self.size > 500 or self.get_from_web:

            if self.size > 2160:
                url = self.moon_domain+self.moon_path+"/frames/5760x3240_16x9_30p/" \
                      f"plain/moon.{self.moon_image_number:04d}.tif"
            elif self.size > 216:
                url = self.moon_domain+self.moon_path+"/frames/3840x2160_16x9_30p/" \
                  f"plain/moon.{self.moon_image_number:04d}.tif"
            else:
                url = self.moon_domain + self.moon_path + "/frames/216x216_1x1_30p/" \
                                                      f"moon.{self.moon_image_number:04d}.jpg"

            if self.debug:
                print(f"Getting image from url: {url}")
            req = requests.get(url)
            self.image = QImage()
            self.image.loadFromData(req.content, "tiff")
            size = self.image.size()
            if self.debug:
                print("Image size: ", size)
            if self.save:
                self.image.save(f"moon/moon.{self.moon_image_number:04d}.tiff")

            offset = (size.width() - size.height()) / 2
            rect = QRect(offset, 0, size.height(), size.height())
            self.image = self.image.copy(rect)
            pix = QPixmap.fromImage(self.image)
            pix = pix.scaled(self.size, self.size, Qt.IgnoreAspectRatio,
                             Qt.SmoothTransformation)
            return pix
        else:
            moon_file = f"moon/moon.{self.moon_image_number:04d}.jpg"
            pix = QPixmap(moon_file)
            pix = pix.scaled(self.size, self.size, Qt.IgnoreAspectRatio,
                             Qt.SmoothTransformation)
            return pix
class ScribbleArea(QWidget):
    def __init__(self, parent=None):
        super(ScribbleArea, self).__init__(parent)
        self.myPenColors = [
            QColor("green"),
            QColor("purple"),
            QColor("red"),
            QColor("blue"),
            QColor("yellow"),
            QColor("pink"),
            QColor("orange"),
            QColor("brown"),
            QColor("grey"),
            QColor("black")
        ]
        self.setAttribute(Qt.WA_AcceptTouchEvents)
        self.setAttribute(Qt.WA_StaticContents)
        self.image = QImage()

        self.modified = False

    def s_print(self):
        pass

    def clearImage(self):
        self.image.fill(qRgb(255, 255, 255))
        self.modified = True
        self.update()

    def openImage(self, fileName):
        loadedImage = QImage()
        if not loadedImage.load(fileName):
            return False

        newSize = loadedImage.size().expandedTo(self.size())
        self.resizeImage(loadedImage, newSize)
        self.image = loadedImage
        self.modified = False
        self.update()
        return True

    def saveImage(self, fileName, fileFormat):

        visibleImage = self.image.copy()
        self.resizeImage(visibleImage, self.size())

        if visibleImage.save(fileName, fileFormat):
            self.modified = False
            return True
        else:
            return False

    def paintEvent(self, event):
        painter = QPainter(self)
        rect = event.rect()
        painter.drawImage(rect.topLeft(), self.image, rect)

    def resizeEvent(self, event):
        if self.width() > self.image.width() or self.height(
        ) > self.image.height():
            newWidth = max(self.width() + 128, self.image.width())
            newHeight = max(self.height() + 128, self.image.height())
            self.resizeImage(self.image, QSize(newWidth, newHeight))
            self.update()
        super(ScribbleArea, self).resizeEvent(event)

    def resizeImage(self, image, newSize):
        if self.image.size() == newSize:
            return

        newImage = QImage(newSize, QImage.Format_RGB32)
        newImage.fill(qRgb(255, 255, 255))
        painter = QPainter(newImage)
        painter.drawImage(QPoint(0, 0), self.image)
        self.image = newImage

    #
    # def print(self, )
    # {
    # #ifndef QT_NO_PRINTER:
    #     QPrinter printer(QPrinter.HighResolution)
    #
    #     QPrintDialog *printDialog = new QPrintDialog(&printer, this)
    #     if (printDialog.exec() == QDialog.Accepted) {
    #         QPainter painter(&printer)
    #         QRect rect = painter.viewport()
    #         QSize size = self.image.size()
    #         size.scale(rect.size(), Qt.KeepAspectRatio)
    #         painter.setViewport(rect.x(), rect.y(), size.width(), size.height())
    #         painter.setWindow(self.image.rect())
    #         painter.drawImage(0, 0, image)
    #     }
    # #endif // QT_NO_PRINTER
    # }
    #
    def event(self, event):
        if event.type() == QEvent.TouchBegin or event.type(
        ) == QEvent.TouchUpdate or event.type() == QEvent.TouchEnd:
            touchPoints = event.touchPoints()
            for touchPoint in touchPoints:
                if touchPoint.state() == Qt.TouchPointStationary:
                    continue
                else:
                    rect = touchPoint.rect()
                    if rect.isEmpty():
                        diameter = 50 * touchPoint.pressure()
                        rect.setSize(QSizeF(diameter, diameter))

                    painter = QPainter(self.image)
                    painter.setPen(Qt.NoPen)
                    painter.setBrush(self.myPenColors[touchPoint.id() %
                                                      len(self.myPenColors)])
                    painter.drawEllipse(rect)
                    painter.end()

                    self.modified = True
                    rad = 2
                    self.update(rect.toRect().adjusted(-rad, -rad, +rad, +rad))

        else:
            return super(ScribbleArea, self).event(event)
        return True
Example #11
0
 def run(self):
     # next() raises a StopIteration exception when the generator ends.
     # If this exception is unhandled by run(), it causes thread termination.
     # If wdg internal C++ object was destroyed by main thread (form closing)
     # a RuntimeError exception is raised and causes thread termination too.
     # Thus, no further synchronization is needed.
     import exiftool
     with exiftool.ExifTool() as e:
         while True:
             try:
                 filename = next(self.fileListGen)
                 # get orientation
                 try:
                     # read metadata from sidecar (.mie) if it exists, otherwise from image file.
                     profile, metadata = e.get_metadata(
                         filename,
                         tags=("colorspace", "profileDescription",
                               "orientation", "model", "rating",
                               "FileCreateDate"),
                         createsidecar=False)
                 except ValueError:
                     metadata = {}
                 # get image info
                 tmp = [
                     value for key, value in metadata.items()
                     if 'orientation' in key.lower()
                 ]
                 orientation = tmp[
                     0] if tmp else 1  # metadata.get("EXIF:Orientation", 1)
                 # EXIF:DateTimeOriginal seems to be missing in many files
                 tmp = [
                     value for key, value in metadata.items()
                     if 'date' in key.lower()
                 ]
                 date = tmp[
                     0] if tmp else ''  # metadata.get("EXIF:ModifyDate", '')
                 tmp = [
                     value for key, value in metadata.items()
                     if 'rating' in key.lower()
                 ]
                 rating = tmp[
                     0] if tmp else 0  # metadata.get("XMP:Rating", 5)
                 rating = ''.join(['*'] * int(rating))
                 transformation = exiftool.decodeExifOrientation(
                     orientation)
                 # get thumbnail
                 img = e.get_thumbNail(filename, thumbname='thumbnailimage')
                 # no thumbnail found : try preview
                 if img.isNull():
                     img = e.get_thumbNail(
                         filename, thumbname='PreviewImage'
                     )  # the order is important : for jpeg PreviewImage is full sized !
                 # all failed : open image
                 if img.isNull():
                     img = QImage(filename)
                 # remove possible black borders, except for .NEF
                 if filename[-3:] not in ['nef', 'NEF']:
                     bBorder = 7
                     img = img.copy(
                         QRect(0, bBorder, img.width(),
                               img.height() - 2 * bBorder))
                 pxm = QPixmap.fromImage(img)
                 if not transformation.isIdentity():
                     pxm = pxm.transformed(transformation)
                 # set item caption and tooltip
                 item = QListWidgetItem(
                     QIcon(pxm), basename(filename))  # + '\n' + rating)
                 item.setToolTip(
                     basename(filename) + ' ' + date + ' ' + rating)
                 # set item mimeData to get filename=item.data(Qt.UserRole)[0] transformation=item.data(Qt.UserRole)[1]
                 item.setData(Qt.UserRole, (filename, transformation))
                 self.wdg.addItem(item)
             # for clean exiting we catch all exceptions and force break
             except OSError:
                 continue
             except:
                 break
Example #12
0
class ImageSegmenterView(QGraphicsView):
    photoClicked = Signal(QPoint)

    def __init__(self, parent):
        super(ImageSegmenterView, self).__init__(parent)
        self._zoom = 0
        self.empty = True
        self._scene = QGraphicsScene(self)
        self._photo = QGraphicsPixmapItem()
        self.image_hidden = False
        self._seglayer = QGraphicsPixmapItem()
        self._seglayer.setOpacity(0.5)
        self._scene.addItem(self._photo)
        self._scene.addItem(self._seglayer)
        self.setScene(self._scene)
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
        # self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        # self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setBackgroundBrush(QBrush(QtCore.Qt.darkGray))
        self.setFrameShape(QFrame.NoFrame)

        self.seg_image = None
        self.start = False
        self.prev_point = None
        self.painter = None
        self.segmenter_pen = QPen(QtCore.Qt.green, 30, QtCore.Qt.SolidLine)
        self.segmenter_pen.setCapStyle(QtCore.Qt.RoundCap)
        self.segmenter_pen.setJoinStyle(QtCore.Qt.RoundJoin)
        self.erase = False
        self.changed = False

        self.history = collections.deque(maxlen=10)
        self.future = collections.deque(maxlen=10)

    def hasPhoto(self):
        return not self.empty

    def fitInView(self, scale=True):
        rect = QtCore.QRectF(self._photo.pixmap().rect())
        if not rect.isNull():
            self.setSceneRect(rect)
            if self.hasPhoto():
                unity = self.transform().mapRect(QtCore.QRectF(0, 0, 1, 1))
                self.scale(1 / unity.width(), 1 / unity.height())
                viewrect = self.viewport().rect()
                scenerect = self.transform().mapRect(rect)
                factor = min(viewrect.width() / scenerect.width(),
                             viewrect.height() / scenerect.height())
                self.scale(factor, factor)
            self._zoom = 0

    def setPhoto(self, pixmap=None):
        self._zoom = 0
        if pixmap and not pixmap.isNull():
            self.empty = False
            self.changed = False
            self._photo.setPixmap(pixmap)
            self.seg_image = QImage(pixmap.width(), pixmap.height(),
                                    QImage.Format_ARGB32_Premultiplied)
            self.seg_image.fill(QtCore.Qt.transparent)
            self._seglayer.setPixmap(QPixmap.fromImage(self.seg_image))
        else:
            self._empty = True
            self._photo.setPixmap(QPixmap())
        self.fitInView()

    def save_state(self):
        if self.future is not None:
            while len(self.future) > 0:
                present = self.future.pop()
                del present
        if self.seg_image is not None:
            self.history.append(self.seg_image.copy())

    def clear_history(self):
        while len(self.history) > 0:
            present = self.history.pop()
            del present

    def undo(self):
        if len(self.history) > 0:
            self.future.append(self.seg_image)
            present = self.history.pop()
            self.seg_image = present
            self._seglayer.setPixmap(QPixmap.fromImage(self.seg_image))

    def redo(self):
        if len(self.future) > 0:
            self.history.append(self.seg_image)
            present = self.future.pop()
            self.seg_image = present
            self._seglayer.setPixmap(QPixmap.fromImage(self.seg_image))

    def setSegLayer(self, pixmap=None):
        if not self._photo.pixmap().isNull():
            self.save_state()
            self.seg_image = QImage(pixmap.toImage())
            self._seglayer.setPixmap(QPixmap.fromImage(self.seg_image))

    def resetSegLayer(self):
        if not self._photo.pixmap().isNull():
            self.changed = True
            self.save_state()
            del self.seg_image
            self.seg_image = QImage(self._photo.pixmap().width(),
                                    self._photo.pixmap().height(),
                                    QImage.Format_ARGB32_Premultiplied)
            self.seg_image.fill(QtCore.Qt.transparent)
            self._seglayer.setPixmap(QPixmap.fromImage(self.seg_image))

    def wheelEvent(self, event):
        if self.hasPhoto() and not self.start:
            if event.angleDelta().y() > 0:
                factor = 1.25
                self._zoom += 1
            else:
                factor = 0.8
                self._zoom -= 1
            if self._zoom > 0:
                self.scale(factor, factor)
            elif self._zoom == 0:
                self.fitInView()
            else:
                self._zoom = 0

    def mousePressEvent(self, event):
        if not self._photo.pixmap().isNull():
            if event.button() == QtCore.Qt.LeftButton:
                self.save_state()
                self.start = True
                self.painter = QPainter(self.seg_image)
                if self.erase:
                    self.painter.setCompositionMode(
                        QPainter.CompositionMode_Clear)
                self.painter.setPen(self.segmenter_pen)
                self.paint_point(event.pos())
            elif event.button() == QtCore.Qt.RightButton:
                if not self._photo.pixmap().isNull():
                    self.setDragMode(QGraphicsView.ScrollHandDrag)
                    self.scroll_origin = self.mapToScene(event.pos())
                # if self._photo.isUnderMouse():
                #     self.photoClicked.emit(self.mapToScene(event.pos()).toPoint())
        super(ImageSegmenterView, self).mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if not self._photo.pixmap().isNull():
            if self.start:
                self.paint_point(event.pos())
            if event.buttons() & QtCore.Qt.RightButton:
                newpoint = self.mapToScene(event.pos())
                translation = newpoint - self.scroll_origin
                self.translate(translation.x(), translation.y())
                self.scroll_origin = self.mapToScene(event.pos())
        super(ImageSegmenterView, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        if not self._photo.pixmap().isNull():
            if self.start:
                self.start = False
                self.prev_point = None
                self.painter.end()
            if self.dragMode() == QGraphicsView.ScrollHandDrag:
                self.setDragMode(QGraphicsView.NoDrag)

    def paint_point(self, pos):
        self.changed = True
        pos = self.mapToScene(pos).toPoint()
        if self.prev_point is not None:
            self.painter.drawLine(self.prev_point, pos)
        else:
            self.painter.drawPoint(pos)
        self.prev_point = pos
        self._seglayer.setPixmap(QPixmap.fromImage(self.seg_image))

    def set_foreground(self):
        self.erase = False
        self.segmenter_pen.setColor(QtCore.Qt.green)

    def set_possible_foreground(self):
        self.erase = False
        self.segmenter_pen.setColor(QtCore.Qt.blue)

    def set_possible_background(self):
        self.erase = True
        self.segmenter_pen.setColor(QtCore.Qt.transparent)

    def set_background(self):
        self.erase = False
        self.segmenter_pen.setColor(QtCore.Qt.red)

    def set_pen_size(self, size):
        self.segmenter_pen.setWidth(size)

    def set_opacity(self, value):
        self._seglayer.setOpacity(value / 100)

    def hide_image(self):
        if self.image_hidden:
            self._photo.setOpacity(1)
            self.image_hidden = False
        else:
            self._photo.setOpacity(0)
            self.image_hidden = True
Example #13
0
class Block:
    SIDE_LENGTH = 2 * Tile.SIDE_LENGTH
    WIDTH = SIDE_LENGTH
    HEIGHT = SIDE_LENGTH

    PIXEL_COUNT = WIDTH * HEIGHT

    tsa_data = bytes()

    _block_cache = {}

    def __init__(
        self,
        block_index: int,
        palette_group: List[List[int]],
        pattern_table: PatternTable,
        tsa_data: bytes,
        mirrored=False,
    ):
        self.index = block_index

        palette_index = (block_index & 0b1100_0000) >> 6

        self.bg_color = QColor(*NESPalette[palette_group[palette_index][0]])

        self._block_id = (block_index, self.bg_color.toTuple(),
                          pattern_table.graphics_set)

        lu = tsa_data[TSA_BANK_0 + block_index]
        ld = tsa_data[TSA_BANK_1 + block_index]
        ru = tsa_data[TSA_BANK_2 + block_index]
        rd = tsa_data[TSA_BANK_3 + block_index]

        self.lu_tile = Tile(lu, palette_group, palette_index, pattern_table)
        self.ld_tile = Tile(ld, palette_group, palette_index, pattern_table)

        if mirrored:
            self.ru_tile = Tile(lu,
                                palette_group,
                                palette_index,
                                pattern_table,
                                mirrored=True)
            self.rd_tile = Tile(ld,
                                palette_group,
                                palette_index,
                                pattern_table,
                                mirrored=True)
        else:
            self.ru_tile = Tile(ru, palette_group, palette_index,
                                pattern_table)
            self.rd_tile = Tile(rd, palette_group, palette_index,
                                pattern_table)

        self.image = QImage(Block.WIDTH, Block.HEIGHT, QImage.Format_RGB888)
        painter = QPainter(self.image)

        painter.drawImage(QPoint(0, 0), self.lu_tile.as_image())
        painter.drawImage(QPoint(Tile.WIDTH, 0), self.ru_tile.as_image())
        painter.drawImage(QPoint(0, Tile.HEIGHT), self.ld_tile.as_image())
        painter.drawImage(QPoint(Tile.WIDTH, Tile.HEIGHT),
                          self.rd_tile.as_image())

        painter.end()

        if _image_only_one_color(self.image) and self.image.pixelColor(
                0, 0) == QColor(*MASK_COLOR):
            self._whole_block_is_transparent = True
        else:
            self._whole_block_is_transparent = False

    def draw(self,
             painter: QPainter,
             x,
             y,
             block_length,
             selected=False,
             transparent=False):
        block_attributes = (self._block_id, block_length, selected,
                            transparent)

        if block_attributes not in Block._block_cache:
            image = self.image.copy()

            if block_length != Block.WIDTH:
                image = image.scaled(block_length, block_length)

            # mask out the transparent pixels first
            mask = image.createMaskFromColor(
                QColor(*MASK_COLOR).rgb(), Qt.MaskOutColor)
            image.setAlphaChannel(mask)

            if not transparent:  # or self._whole_block_is_transparent:
                image = self._replace_transparent_with_background(image)

            if selected:
                apply_selection_overlay(image, mask)

            Block._block_cache[block_attributes] = image

        painter.drawImage(x, y, Block._block_cache[block_attributes])

    def _replace_transparent_with_background(self, image):
        # draw image on background layer, to fill transparent pixels
        background = image.copy()
        background.fill(self.bg_color)

        _painter = QPainter(background)
        _painter.drawImage(QPoint(), image)
        _painter.end()

        return background
Example #14
0
class QLayer(vImage):
    """
    Base class for image layers
    """
    @classmethod
    def fromImage(cls, mImg, parentImage=None):
        """
        Returns a QLayer object initialized with mImg.
        @param mImg:
        @type mImg: QImage
        @param parentImage:
        @type parentImage: mImage
        @return:
        @rtype: Qlayer
        """
        layer = QLayer(QImg=mImg, parentImage=parentImage)
        layer.parentImage = parentImage
        return layer

    def __init__(self, *args, **kwargs):
        ############################################################
        # Signals
        # Making QLayer inherit from QObject leads to
        # a bugged behavior of hasattr and getattr.
        # So, we don't add signals as first level class attributes.
        # Instead, we use instances of ad hoc signal containers.
        ############################################################
        self.visibilityChanged = baseSignal_bool()
        self.colorPicked = baseSignal_Int2()
        self.selectionChanged = baseSignal_No()
        self.maskSettingsChanged = baseSignal_No()
        ###########################################################
        # when a geometric transformation is applied to the whole image
        # each layer must be replaced with a transformed layer, recorded in tLayer
        # and tLayer.parentLayer keeps a reference to the original layer.
        ###########################################################
        self.tLayer = self
        self.parentLayer = self
        self.modified = False
        self.name = 'noname'
        self.visible = True
        self.isClipping = False
        self.role = kwargs.pop('role', '')
        self.tool = None
        # back link to parent image
        parentImage = kwargs.pop('parentImage', None)
        self.parentImage = weakProxy(parentImage)
        super().__init__(*args, **kwargs)  # don't move backwards
        # mask init, must be done after after calling super().__init__
        if type(self) not in [QPresentationLayer]:
            self.mask = QImage(self.width(), self.height(),
                               QImage.Format_ARGB32)
            # default : unmask all
            self.mask.fill(self.defaultColor_UnMasked)
        # layer opacity, range 0.0...1.0
        self.opacity = 1.0
        self.compositionMode = QPainter.CompositionMode_SourceOver
        # The next two attributes are used by adjustment layers only.
        self.execute = lambda l=None, pool=None: l.updatePixmap(
        ) if l is not None else None
        self.options = {}
        # actionName is used by methods graphics***.writeToStream()
        self.actionName = 'actionNull'
        # view is the dock widget containing
        # the graphics form associated with the layer
        self.view = None
        # undo/redo mask history
        self.historyListMask = historyList(size=5)
        # consecutive layers can be grouped.
        # A group is a list of QLayer objects
        self.group = []
        # layer offsets
        self.xOffset, self.yOffset = 0, 0
        self.Zoom_coeff = 1.0
        # clone dup layer shift and zoom  relative to current layer
        self.xAltOffset, self.yAltOffset = 0, 0
        self.AltZoom_coeff = 1.0
        self.updatePixmap()

    def getGraphicsForm(self):
        """
        Return the graphics form associated with the layer
        @return:
        @rtype: QWidget
        """
        if self.view is not None:
            return self.view.widget()
        return None

    def isActiveLayer(self):
        if self.parentImage.getActiveLayer() is self:
            return True
        return False

    def getMmcSpline(self):
        """
        Returns the spline used for multimode contrast
        correction if it is initialized, and None otherwise.
        @return:
        @rtype: activeSpline
        """
        # get layer graphic form
        grf = self.getGraphicsForm()
        # manual curve form
        if grf.contrastForm is not None:
            return grf.contrastForm.scene().cubicItem
        return None

    def addTool(self, tool):
        """
        Adds tool to layer
        @param tool:
        @type tool: rotatingTool
        """
        self.tool = tool
        tool.modified = False
        tool.layer = self
        try:
            tool.layer.visibilityChanged.sig.disconnect(
            )  # TODO signals removed 30/09/18 validate
        except RuntimeError:
            pass
        tool.layer.visibilityChanged.sig.connect(
            tool.setVisible)  # TODO signals removed 30/09/18 validate
        tool.img = self.parentImage
        w, h = tool.img.width(), tool.img.height()
        for role, pos in zip(
            ['topLeft', 'topRight', 'bottomRight', 'bottomLeft'],
            [QPoint(0, 0),
             QPoint(w, 0),
             QPoint(w, h),
             QPoint(0, h)]):
            tool.btnDict[role].posRelImg = pos
            tool.btnDict[role].posRelImg_ori = pos
            tool.btnDict[role].posRelImg_frozen = pos
        tool.moveRotatingTool()

    def setVisible(self, value):
        """
        Sets self.visible to value and emit visibilityChanged.sig
        @param value:
        @type value: bool
        """
        self.visible = value
        self.visibilityChanged.sig.emit(
            value)  # TODO signals removed 30/09/18 validate

    def bTransformed(self, transformation, parentImage):
        """
        Apply transformation to a copy of layer. Returns the transformed copy.
        @param transformation:
        @type transformation: QTransform
        @param parentImage:
        @type parentImage: vImage
        @return: transformed layer
        @rtype: QLayer
        """
        # init a new layer from transformed image :
        # all static attributes (caches...) are reset to default, but thumb
        tLayer = QLayer.fromImage(self.transformed(transformation),
                                  parentImage=parentImage)
        # copy  dynamic attributes from old layer
        for a in self.__dict__.keys():
            if a not in tLayer.__dict__.keys():
                tLayer.__dict__[a] = self.__dict__[a]
        tLayer.name = self.name
        tLayer.actionName = self.actionName
        tLayer.view = self.view
        tLayer.visible = self.visible  # TODO added 25/09/18 validate
        # cut link from old layer to graphic form
        # self.view = None                        # TODO 04/12/17 validate
        tLayer.execute = self.execute
        tLayer.mask = self.mask.transformed(transformation)
        tLayer.maskIsEnabled, tLayer.maskIsSelected = self.maskIsEnabled, self.maskIsSelected
        return tLayer

    def initThumb(self):
        """
        Override vImage.initThumb, to set the parentImage attribute
        """
        super().initThumb()
        self.thumb.parentImage = self.parentImage

    def initHald(self):
        """
        Build a hald image (as a QImage) from identity 3D LUT.
        """
        if not self.cachesEnabled:
            return
        s = int(LUT3DIdentity.size**(3.0 / 2.0)) + 1
        buf0 = LUT3DIdentity.toHaldArray(s, s).haldBuffer
        # self.hald = QLayer(QImg=QImage(QSize(190,190), QImage.Format_ARGB32))
        self.hald = QImage(QSize(s, s), QImage.Format_ARGB32)
        buf1 = QImageBuffer(self.hald)
        buf1[:, :, :3] = buf0
        buf1[:, :, 3] = 255
        self.hald.parentImage = self.parentImage

    def getHald(self):
        if not self.cachesEnabled:
            s = int(LUT3DIdentity.size**(3.0 / 2.0)) + 1
            buf0 = LUT3DIdentity.toHaldArray(s, s).haldBuffer
            # self.hald = QLayer(QImg=QImage(QSize(190,190), QImage.Format_ARGB32))
            hald = QImage(QSize(s, s), QImage.Format_ARGB32)
            buf1 = QImageBuffer(hald)
            buf1[:, :, :3] = buf0
            buf1[:, :, 3] = 255
            hald.parentImage = self.parentImage
            return hald
        if self.hald is None:
            self.initHald()
        return self.hald

    def getCurrentImage(self):
        """
        Returns current (full, preview or hald) image, according to
        the value of the flags useThumb and useHald. The thumbnail and hald
        are computed if they are not initialized.
        Otherwise, they are not updated unless self.thumb is
        None or purgeThumb is True.
        Overrides vImage method
        @return: current image
        @rtype: QLayer
        """
        if self.parentImage.useHald:
            return self.getHald()
        if self.parentImage.useThumb:
            return self.getThumb()
        else:
            return self

    def inputImg(self, redo=True):
        """
        return maskedImageContainer/maskedThumbContainer.
        If redo is True(default), containers are updated.
        layer.applyToStack() always calls inputImg() with redo=True.
        So, to keep the containers up to date we only have to follow
        each layer modification with a call to layer.applyToStack().
        @param redo:
        @type redo: boolean
        @return:
        @rtype: bImage
        """
        lower = self.parentImage.layersStack[self.getLowerVisibleStackIndex()]
        container = lower.maskedThumbContainer if self.parentImage.useThumb else lower.maskedImageContainer
        if redo or container is None:
            container = lower.getCurrentMaskedImage()
            container.rPixmap = None  # invalidate and don't update
        else:
            if container.rPixmap is None:
                container.rPixmap = QPixmap.fromImage(
                    container)  # will probably be reused : update
        return container

    def full2CurrentXY(self, x, y):
        """
        Maps x,y coordinates of pixel in the full image to
        coordinates in current image.
        @param x:
        @type x: int or float
        @param y:
        @type y: int or float
        @return:
        @rtype: 2uple of int
        """
        if self.parentImage.useThumb:
            currentImg = self.getThumb()
            x = (x * currentImg.width()) / self.width()
            y = (y * currentImg.height()) / self.height()
        return int(x), int(y)

    def getCurrentMaskedImage(self):
        """
        Blend the layer stack up to self (included),
        taking into account the masks. The method uses the
        non color managed rPixmap to build the masked image.
        For convenience, mainly to be able to use its color space buffers,
        the built image is of type bImage. It is drawn on a container image,
        instantiated only once.
        @return: masked image
        @rtype: bImage
        """
        # init containers if needed. They are instantiated only
        # once and updated by drawing.
        if self.parentImage.useHald:
            return self.getHald()
        if self.maskedThumbContainer is None:
            self.maskedThumbContainer = bImage.fromImage(
                self.getThumb(), parentImage=self.parentImage)
        if self.maskedImageContainer is None:
            self.maskedImageContainer = bImage.fromImage(
                self, parentImage=self.parentImage)
        if self.parentImage.useThumb:
            img = self.maskedThumbContainer
        else:
            img = self.maskedImageContainer
        # draw lower stack
        qp = QPainter(img)
        top = self.parentImage.getStackIndex(self)
        bottom = 0
        for i, layer in enumerate(self.parentImage.layersStack[bottom:top +
                                                               1]):
            if layer.visible:
                if i == 0:
                    qp.setCompositionMode(QPainter.CompositionMode_Source)
                else:
                    qp.setOpacity(layer.opacity)
                    qp.setCompositionMode(layer.compositionMode)
                if layer.rPixmap is None:
                    layer.rPixmap = QPixmap.fromImage(layer.getCurrentImage(
                    ))  # TODO modified 9/12/18 validate
                qp.drawPixmap(QRect(0, 0, img.width(), img.height()),
                              layer.rPixmap)
                # clipping
                if layer.isClipping and layer.maskIsEnabled:
                    # draw mask as opacity mask
                    # mode DestinationIn (set dest opacity to source opacity)
                    qp.setCompositionMode(
                        QPainter.CompositionMode_DestinationIn)
                    omask = vImage.color2OpacityMask(layer.mask)
                    qp.drawImage(QRect(0, 0, img.width(), img.height()), omask)
        qp.end()
        return img

    def applyToStack(self):
        """
        Apply new layer parameters and propagate changes to upper layers.
        """

        # recursive function
        def applyToStack_(layer, pool=None):
            # apply transformation
            if layer.visible:
                start = time()
                layer.execute(l=layer)
                layer.cacheInvalidate()
                print("%s %.2f" % (layer.name, time() - start))
            stack = layer.parentImage.layersStack
            lg = len(stack)
            ind = layer.getStackIndex() + 1
            # update histograms displayed
            # on the layer form, if any
            if ind < lg:
                grForm = stack[ind].getGraphicsForm()
                if grForm is not None:
                    grForm.updateHists()
            # get next upper visible layer
            while ind < lg:
                if stack[ind].visible:
                    break
                ind += 1
            if ind < lg:
                layer1 = stack[ind]
                applyToStack_(layer1, pool=pool)

        try:
            QApplication.setOverrideCursor(Qt.WaitCursor)
            QApplication.processEvents()
            applyToStack_(self, pool=None)
            # update the presentation layer
            self.parentImage.prLayer.execute(l=None, pool=None)
        finally:
            self.parentImage.setModified(True)
            QApplication.restoreOverrideCursor()
            QApplication.processEvents()

    """
    def applyToStackIter(self):
        #iterative version of applyToStack
        stack = self.parentImage.layersStack
        ind = self.getStackIndex() + 1
        try:
            QApplication.setOverrideCursor(Qt.WaitCursor)
            QApplication.processEvents()
            self.execute()
            for layer in stack[ind:]:
                if layer.visible:
                    layer.cacheInvalidate()
                    # for hald friendly layer compute output hald, otherwise compute output image
                    layer.execute()
        finally:
            QApplication.restoreOverrideCursor()
            QApplication.processEvents()
    """

    def isAdjustLayer(self):
        return self.view is not None  # hasattr(self, 'view')

    def isSegmentLayer(self):
        return 'SEGMENT' in self.role

    def isCloningLayer(self):
        return 'CLONING' in self.role

    def isGeomLayer(self):
        return 'GEOM' in self.role

    def is3DLUTLayer(self):
        return '3DLUT' in self.role

    def isRawLayer(self):
        return 'RAW' in self.role

    def updatePixmap(self, maskOnly=False):
        """
        Synchronize rPixmap with the layer image and mask.
        if maskIsEnabled is False, the mask is not used.
        If maskIsEnabled is True, then
            - if maskIsSelected is True, the mask is drawn over
              the layer as a color mask.
            - if maskIsSelected is False, the mask is drawn as an
              opacity mask, setting the image opacity to that of the mask
              (mode DestinationIn).
        @param maskOnly: not used : for consistency with overriding method signature
        @type maskOnly: boolean
        """
        rImg = self.getCurrentImage()
        # apply layer transformation. Missing pixels are set to QColor(0,0,0,0)
        if self.xOffset != 0 or self.yOffset != 0:
            x, y = self.full2CurrentXY(self.xOffset, self.yOffset)
            rImg = rImg.copy(
                QRect(-x, -y,
                      rImg.width() * self.Zoom_coeff,
                      rImg.height() * self.Zoom_coeff))
        if self.maskIsEnabled:
            rImg = vImage.visualizeMask(rImg,
                                        self.mask,
                                        color=self.maskIsSelected,
                                        clipping=True)  # self.isClipping)
        self.rPixmap = QPixmap.fromImage(rImg)
        self.setModified(True)

    def getStackIndex(self):
        """
        Returns layer index in the stack, len(stack) - 1 if
        the layer is not in the stack.
        @return:
        @rtype: int
        """
        i = -1  # TODO added 5/11/18 validate
        for i, l in enumerate(self.parentImage.layersStack):
            if l is self:
                break
        return i

    def setMaskEnabled(self, color=False):
        self.maskIsEnabled = True
        self.maskIsSelected = color
        self.maskSettingsChanged.sig.emit()

    def getTopVisibleStackIndex(self):
        """
        Returns the index of the top visible layer
        @return:
        @rtype:
        """
        stack = self.parentImage.layersStack
        lg = len(stack)
        for i in range(lg - 1, -1, -1):
            if stack[i].visible:
                return i
        return -1

    def getLowerVisibleStackIndex(self):
        """
        Returns the index of the next lower visible layer,
        -1 if it does not exists
        @return:
        @rtype: int
        """
        ind = self.getStackIndex()
        stack = self.parentImage.layersStack
        for i in range(ind - 1, -1, -1):
            if stack[i].visible:
                return i
        return -1

    def getUpperVisibleStackIndex(self):
        """
        Returns the index of the next upper visible layer,
        -1 if it does not exists
        @return:
        @rtype: int
        """
        ind = self.getStackIndex()
        stack = self.parentImage.layersStack
        lg = len(stack)
        for i in range(ind + 1, lg, 1):
            if stack[i].visible:
                return i
        return -1

    def getLowerClippingStackIndex(self):
        """
         Returns the index of the next lower clipping layer,
        -1 if it does not exists

        @return:
        @rtype: int
        """
        ind = self.getStackIndex()
        for i in range(ind + 1, len(self.parentImage.layersStack), 1):
            if self.parentImage.layersStack[i].isClipping:
                return i
        return -1

    def linkMask2Lower(self):
        """
        share mask with next lower layer
        @return:
        @rtype:
        """
        ind = self.getStackIndex()
        if ind == 0:
            return
        lower = self.parentImage.layersStack[ind - 1]
        # don't link two groups
        if self.group and lower.group:
            return
        if not self.group and not lower.group:
            self.group = [self, lower]
            lower.group = self.group
        elif not lower.group:
            if not any(o is lower for o in self.group):
                self.group.append(lower)
            lower.group = self.group
        elif not self.group:
            if not any(item is self for item in lower.group):
                lower.group.append(self)
            self.group = lower.group
        self.mask = lower.mask

    def unlinkMask(self):
        self.mask = self.mask.copy()
        # remove self from group
        for i, item in enumerate(self.group):
            if item is self:
                self.group.pop(i)
                # don't keep  group with length 1
                if len(self.group) == 1:
                    self.group.pop(0)
                break
        self.group = []

    def merge_with_layer_immediately_below(self):
        """
        Merges a layer with the next lower visible layer. Does nothing
        if mode is preview or the target layer is an adjustment layer.
        """
        if not hasattr(self, 'inputImg'):
            return
        ind = self.getLowerVisibleStackIndex()
        if ind < 0:
            # no visible layer found
            return
        target = self.parentImage.layersStack[ind]
        if hasattr(target, 'inputImg') or self.parentImage.useThumb:
            info = "Uncheck Preview first" if self.parentImage.useThumb else "Target layer must be background or image"
            dlgWarn("Cannot Merge layers", info=info)
            return
        # update stack
        self.parentImage.layersStack[0].applyToStack()
        # merge
        # target.setImage(self)
        qp = QPainter(target)
        qp.setCompositionMode(self.compositionMode)
        qp.setOpacity(self.opacity)
        qp.drawImage(QRect(0, 0, self.width(), self.height()), self)
        target.updatePixmap()
        self.parentImage.layerView.clear(delete=False)
        currentIndex = self.getStackIndex()
        self.parentImage.activeLayerIndex = ind
        self.parentImage.layersStack.pop(currentIndex)
        self.parentImage.layerView.setLayers(self.parentImage)

    def reset(self):
        """
        reset layer to inputImg
        """
        self.setImage(self.inputImg())

    def setOpacity(self, value):
        """
        set layer opacity to value/100.0
        @param value:
        @type value: int in range 0..100
        """
        self.opacity = value / 100.0

    def setColorMaskOpacity(self, value):
        """
        Set mask alpha channel to value
        @param value:
        @type value: int in range 0..255
        """
        self.colorMaskOpacity = value
        buf = QImageBuffer(self.mask)
        buf[:, :, 3] = np.uint8(value)

    def readFromStream(self, dataStream):
        grForm = self.getGraphicsForm()
        if grForm is not None:
            grForm.readFromStream(dataStream)
        return dataStream
Example #15
0
class Canvas(QWidget):
    content_changed = Signal()

    _background_color = QColor.fromRgb(0, 0, 0)
    _foreground_color = QColor.fromRgb(255, 255, 255)

    def __init__(self, parent, w, h, pen_width, scale):
        super().__init__(parent)
        self.w = w
        self.h = h
        self.scaled_w = scale * w
        self.scaled_h = scale * h
        self.scale = scale

        # Set size
        self.setFixedSize(self.scaled_w, self.scaled_h)

        # Create image
        self.small_image = QImage(self.w, self.h, QImage.Format_RGB32)
        self.small_image.fill(self._background_color)
        self.large_image = QImage(self.scaled_w, self.scaled_h,
                                  QImage.Format_RGB32)
        self.large_image.fill(self._background_color)

        # Create pen
        self.pen = QPen()
        self.pen.setColor(self._foreground_color)
        self.pen.setJoinStyle(Qt.RoundJoin)
        self.pen.setCapStyle(Qt.RoundCap)
        self.pen.setWidthF(scale * pen_width)

        # There is currently no path
        self.currentPath = None

        self.content_changed.connect(self.repaint)

    def _get_painter(self, paintee):
        painter = QPainter(paintee)
        painter.setPen(self.pen)
        painter.setRenderHint(QPainter.Antialiasing, True)
        return painter

    def _derive_small_image(self, large_image=None):
        if large_image is None:
            large_image = self.large_image
        # Downsample image
        self.small_image = large_image.scaled(self.w,
                                              self.h,
                                              mode=Qt.SmoothTransformation)
        self.content_changed.emit()

    def _current_path_updated(self, terminate_path=False):
        # Determine whether to draw on the large image directly or whether to make a temporary copy
        paintee = self.large_image if terminate_path else self.large_image.copy(
        )

        # Draw path on the large image of choice
        painter = self._get_painter(paintee)
        if self.currentPath.elementCount() != 1:
            painter.drawPath(self.currentPath)
        else:
            painter.drawPoint(self.currentPath.elementAt(0))
        painter.end()

        # Optionally terminate the path
        if terminate_path:
            self.currentPath = None

        # Downsample image
        self._derive_small_image(paintee)

    def _clear_image(self):
        self.large_image.fill(self._background_color)
        self._derive_small_image()

    def get_content(self):
        return np.asarray(self.small_image.constBits()).reshape(
            (self.h, self.w, -1))

    def set_content(self, image_rgb):
        for row in range(image_rgb.shape[0]):
            for col in range(image_rgb.shape[1]):
                self.small_image.setPixel(col, row, image_rgb[row, col])
        self.large_image = self.small_image.scaled(
            self.scaled_w, self.scaled_h, mode=Qt.SmoothTransformation)
        self._derive_small_image()
        self.content_changed.emit()

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            # Create new path
            self.currentPath = QPainterPath()
            self.currentPath.moveTo(event.pos())
            self._current_path_updated()

    def mouseMoveEvent(self, event):
        if (event.buttons() & Qt.LeftButton) and self.currentPath is not None:
            # Add point to current path
            self.currentPath.lineTo(event.pos())
            self._current_path_updated()

    def mouseReleaseEvent(self, event):
        if (event.button() == Qt.LeftButton) and self.currentPath is not None:
            # Add terminal point to current path
            self.currentPath.lineTo(event.pos())
            self._current_path_updated(terminate_path=True)
        elif event.button() == Qt.RightButton:
            self._clear_image()

    def paintEvent(self, event):
        paint_rect = event.rect()  # Only paint the surface that needs painting
        painter = self._get_painter(self)

        # Draw image
        painter.scale(self.scale, self.scale)
        painter.drawImage(paint_rect, self.small_image, paint_rect)

        painter.end()

        painter = self._get_painter(self)

        #if self.currentPath is not None:
        #    painter.drawPath(self.currentPath)

    @Slot()
    def repaint(self):
        super().repaint()
Example #16
0
class Map(QQuickPaintedItem):

    def __init__(self, parent = None):
        super(Map, self).__init__(parent)

        self.viewport = None

        self.setAcceptedMouseButtons(Qt.AllButtons)
        self.generate()

    clicked = Signal(QPoint)

    @Slot()
    def generate(self):
        # QImage does not work with bits, only with bytes
        self.image = QImage(300, 300, QImage.Format_Grayscale8)
        self.oldImage = None
        self.generateMap()
        self._percentage = 0

        self._pressClick = QPoint()

    def pixel(self, x: int, y: int, image = None) -> bool:
        if not image:
            image = self.image

        # This solves:
        # QImage::pixelIndex: Not applicable for 8-bpp images (no palette)
        return image.bits()[int(x) + int(y) * image.bytesPerLine()] & 0xff

    def setPixel(self, x: int, y: int, value: int, image = None):
        if not image:
            image = self.image

        # This solves:
        # QImage::pixelIndex: Not applicable for 8-bpp images (no palette)
        image.bits()[int(x) + int(y) * image.bytesPerLine()] = value

    def createRandomMap(self):
        for i in range(self.image.byteCount()):
            if random.random() < 0.35:
                self.image.bits()[i] = 255
            else:
                self.image.bits()[i] = 0

    def countNeighbors(self, x: int, y: int, image = None, n = 1) -> int:
        if not image:
            image = self.image

        count = 0
        for i in range(-n , n + 1):
            for u in range(-n , n + 1):
                if not i and not u:
                    continue

                #TODO: pixel function is poor
                # need to use bits()
                if x + i < 0 or y + u < 0 or \
                    x + i >= image.width() or y + u >= image.height():
                    count += 1
                    continue

                if not self.pixel(x + i, y + u, image):
                    count += 1

        return count

    @Slot(int)
    def doStep(self, n = 1):
        if self.image.format() != QImage.Format_Grayscale8:
            print("Wrong file format, generate map again.")
            return

        deathLimit = 14
        self._percentage = 0
        for _ in range(n):
            _image = self.image.copy()
            for x in range(self.image.width()):
                self._percentage += 1.0/(self.image.width()*n)
                if x%10 == 0:
                    # Update percentage
                    self.percentageChanged.emit()
                    # processEvent is necessary
                    QEventLoop().processEvents()
                    # Update map
                    self.update()
                for y in range(self.image.height()):
                    if self.countNeighbors(x, y, _image, 2) > deathLimit or \
                        x == 0 or y == 0 or x == _image.width() - 1 or y == _image.height() - 1:
                        self.setPixel(x, y, 0)
                    else:
                        self.setPixel(x, y, 255)
        # Update percentage
        self.update()
        self.oldImage = self.image.copy()
        self.percentageChanged.emit()
        QEventLoop().processEvents()

    def generateMap(self):
        self.createRandomMap()
        self.update()
        self.oldImage = self.image.copy()

    def paint(self, painter):
        painter.drawImage(QRect(0, 0, self.width(), self.height()), self.image)
        self.viewport = painter.viewport()

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Space:
            print('Update..')
            start = time.time()
            self.doStep()
            print('Took: %.2fs' % (time.time() - start))
        event.accept()

    def mousePressEvent(self, event):
        a, b = event.pos().x()*self.image.width()/self.width(), event.pos().y()*self.image.height()/self.height()
        self.clicked.emit(QPoint(a, b))

    def mouseMoveEvent(self, event):
        a, b = event.pos().x()*self.image.width()/self.width(), event.pos().y()*self.image.height()/self.height()
        self.clicked.emit(QPoint(a, b))

    def percentage(self):
        return self._percentage

    percentageChanged = Signal()
    percentage = Property(float, percentage, notify=percentageChanged)

    @Slot()
    def addVehicle(self):
        self.image = self.image.convertToFormat(QImage.Format_RGBA8888)
        painter = QPainter(self.image)
        painter.drawImage(QRect(50, 50, 13, 15), QImage("imgs/turtle.png"))
        painter.end()
        self.update()

    @Slot(str)
    def saveMap(self, path: str):
        # Check image encoder
        if(self.image.format() != QImage.Format_Grayscale8):
            print('Wrong map pixel format, create map without vehicle added.')
            return
        ok = self.image.save(path)
        if(not ok):
            print('It was not possible to save Map in:', path)

    @Slot(str)
    def loadMap(self, path: str):
        # Check image encoder
        image = QImage(path)
        if(image.format() != QImage.Format_Grayscale8):
            print('Wrong map pixel format, map should be Grayscale8.')
            return
        self.image = image
        self.oldImage = self.image.copy()
        self.update()