[QT4, QPointer] Rysowanie za pomocą myszki


(jmmzon) #1

Byłbym wdzięczny gdyby ktoś (posiadający chwile wolnego czasu) napisał kod programu za pomocą którego można by było narysować przy użyciu myszy linie. Chodzi mi o to by mieć jakikolwiek punkt zaczepienia, odnośnie tego jak takie rzeczy się obsługuje w Qt. Z góry dziękuje za wszelkie konstruktywne informacje.


(Krystian Rosinski) #2

Witam,

Nie sądzę, żeby ktoś tutaj podjął się napisania takiego programu. Szybciej będzie skorzystać z gotowych przykładów, np. Scribble.

Ten sam przykład napisany w Pythonie:

# -*- coding: utf-8 -*-


import sip

sip.setapi('QString', 2)

sip.setapi('QVariant', 2)


from PyQt4 import QtCore, QtGui


class ScribbleArea(QtGui.QWidget):


    def __init__ (self, parent=None):

        super(ScribbleArea, self). __init__ (parent)


        self.setAttribute(QtCore.Qt.WA_StaticContents)

        self.modified = False

        self.scribbling = False

        self.myPenWidth = 1

        self.myPenColor = QtCore.Qt.blue

        imageSize = QtCore.QSize(500, 500)

        self.image = QtGui.QImage(imageSize, QtGui.QImage.Format_RGB32)

        self.lastPoint = QtCore.QPoint()


    def openImage(self, fileName):

        loadedImage = QtGui.QImage()

        if not loadedImage.load(fileName):

            return False


        w = loadedImage.width()

        h = loadedImage.height()    

        self.mainWindow.resize(w, h)

        self.image = loadedImage

        self.modified = False

        self.update()

        return True


    def saveImage(self, fileName, fileFormat):

        visibleImage = self.image

        self.resizeImage(visibleImage, self.size())


        if visibleImage.save(fileName, fileFormat):

            self.modified = False

            return True

        else:

            return False


    def setPenColor(self, newColor):

        self.myPenColor = newColor


    def setPenWidth(self, newWidth):

        self.myPenWidth = newWidth


    def clearImage(self):

        self.image.fill(QtGui.qRgb(255, 255, 255))

        self.modified = True

        self.update()


    def mousePressEvent(self, event):

        if event.button() == QtCore.Qt.LeftButton:

            self.lastPoint = event.pos()

            self.scribbling = True


    def mouseMoveEvent(self, event):

        if (event.buttons() QtCore.Qt.LeftButton) and self.scribbling:

            self.drawLineTo(event.pos())


    def mouseReleaseEvent(self, event):

        if event.button() == QtCore.Qt.LeftButton and self.scribbling:

            self.drawLineTo(event.pos())

            self.scribbling = False


    def paintEvent(self, event):

        painter = QtGui.QPainter(self)

        painter.drawImage(event.rect(), self.image)


    def resizeEvent(self, event):

        self.resizeImage(self.image, event.size())

        super(ScribbleArea, self).resizeEvent(event)


    def drawLineTo(self, endPoint):

        painter = QtGui.QPainter(self.image)

        painter.setPen(QtGui.QPen(self.myPenColor, self.myPenWidth,

            QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))

        painter.drawLine(self.lastPoint, endPoint)

        self.modified = True


        self.update()

        self.lastPoint = QtCore.QPoint(endPoint)


    def resizeImage(self, image, newSize):

        if image.size() == newSize:

            return


        newImage = QtGui.QImage(newSize, QtGui.QImage.Format_RGB32)

        newImage.fill(QtGui.qRgb(255, 255, 255))

        painter = QtGui.QPainter(newImage)

        painter.drawImage(QtCore.QPoint(0, 0), image)

        self.image = newImage


    def print_(self):

        printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution)


        printDialog = QtGui.QPrintDialog(printer, self)

        if printDialog.exec_() == QtGui.QDialog.Accepted:

            painter = QtGui.QPainter(printer)

            rect = painter.viewport()

            size = self.image.size()

            size.scale(rect.size(), QtCore.Qt.KeepAspectRatio)

            painter.setViewport(rect.x(), rect.y(), size.width(), size.height())

            painter.setWindow(self.image.rect())

            painter.drawImage(0, 0, self.image)

            painter.end()


    def isModified(self):

        return self.modified


    def penColor(self):

        return self.myPenColor


    def penWidth(self):

        return self.myPenWidth



class MainWindow(QtGui.QMainWindow):

    def __init__ (self):

        super(MainWindow, self). __init__ ()


        self.saveAsActs = []


        self.scribbleArea = ScribbleArea(self)

        self.scribbleArea.clearImage()

        self.scribbleArea.mainWindow = self

        self.setCentralWidget(self.scribbleArea)


        self.createActions()

        self.createMenus()


        self.setWindowTitle("Scribble")

        self.resize(500, 500)


    def closeEvent(self, event):

        if self.maybeSave():

            event.accept()

        else:

            event.ignore()


    def open(self):

        if self.maybeSave():

            fileName = QtGui.QFileDialog.getOpenFileName(self, "Open File",

                QtCore.QDir.currentPath())

            if fileName:

                self.scribbleArea.openImage(fileName)


    def save(self):

        action = self.sender()

        fileFormat = action.data()

        self.saveFile(fileFormat)


    def penColor(self):

        newColor = QtGui.QColorDialog.getColor(self.scribbleArea.penColor())

        if newColor.isValid():

            self.scribbleArea.setPenColor(newColor)


    def penWidth(self):

        newWidth, ok = QtGui.QInputDialog.getInteger(self, "Scribble",

            "Select pen width:", self.scribbleArea.penWidth(), 1, 50, 1)

        if ok:

            self.scribbleArea.setPenWidth(newWidth)


    def about(self):

        QtGui.QMessageBox.about(self, "About Scribble",

            "
The Scribble example shows how to use "

            "QMainWindow as the base widget for an application, and how "

            "to reimplement some of QWidget's event handlers to receive "

            "the events generated for the application's widgets:"

            "
 We reimplement the mouse event handlers to facilitate "

            "drawing, the paint event handler to update the application "

            "and the resize event handler to optimize the application's "

            "appearance. In addition we reimplement the close event "

            "handler to intercept the close events before terminating "

            "the application."

            "
 The example also demonstrates how to use QPainter to "

            "draw an image in real time, as well as to repaint "

            "widgets.")


    def createActions(self):

        self.openAct = QtGui.QAction("Open...", self, shortcut="Ctrl+O",

            triggered=self.open)


        for format in QtGui.QImageWriter.supportedImageFormats():

            format = str(format)


            text = format.upper() + "..."


            action = QtGui.QAction(text, self, triggered=self.save)

            action.setData(format)

            self.saveAsActs.append(action)


        self.printAct = QtGui.QAction("Print...", self,

            triggered=self.scribbleArea.print_)


        self.exitAct = QtGui.QAction("Exit", self, shortcut="Ctrl+Q",

            triggered=self.close)


        self.penColorAct = QtGui.QAction("Pen Color...", self,

            triggered=self.penColor)


        self.penWidthAct = QtGui.QAction("Pen Width...", self,

            triggered=self.penWidth)


        self.clearScreenAct = QtGui.QAction("Clear Screen", self,

            shortcut="Ctrl+L", triggered=self.scribbleArea.clearImage)


        self.aboutAct = QtGui.QAction("About", self, triggered=self.about)


        self.aboutQtAct = QtGui.QAction("About Qt", self,

            triggered=QtGui.qApp.aboutQt)


    def createMenus(self):

        self.saveAsMenu = QtGui.QMenu("Save As", self)

        for action in self.saveAsActs:

            self.saveAsMenu.addAction(action)


        fileMenu = QtGui.QMenu("File", self)

        fileMenu.addAction(self.openAct)

        fileMenu.addMenu(self.saveAsMenu)

        fileMenu.addAction(self.printAct)

        fileMenu.addSeparator()

        fileMenu.addAction(self.exitAct)


        optionMenu = QtGui.QMenu("Options", self)

        optionMenu.addAction(self.penColorAct)

        optionMenu.addAction(self.penWidthAct)

        optionMenu.addSeparator()

        optionMenu.addAction(self.clearScreenAct)


        helpMenu = QtGui.QMenu("Help", self)

        helpMenu.addAction(self.aboutAct)

        helpMenu.addAction(self.aboutQtAct)


        self.menuBar().addMenu(fileMenu)

        self.menuBar().addMenu(optionMenu)

        self.menuBar().addMenu(helpMenu)


    def maybeSave(self):

        if self.scribbleArea.isModified():

            ret = QtGui.QMessageBox.warning(self, "Scribble",

                "The image has been modified.\n"

                "Do you want to save your changes?",

                QtGui.QMessageBox.Save | QtGui.QMessageBox.Discard |

                QtGui.QMessageBox.Cancel)

            if ret == QtGui.QMessageBox.Save:

                return self.saveFile('png')

            elif ret == QtGui.QMessageBox.Cancel:

                return False


        return True


    def saveFile(self, fileFormat):

        initialPath = QtCore.QDir.currentPath() + '/untitled.' + fileFormat


        fileName = QtGui.QFileDialog.getSaveFileName(self, "Save As",

            initialPath,

            "%s Files (*.%s);;All Files (*)" % (fileFormat.upper(), fileFormat))

        if fileName:

            return self.scribbleArea.saveImage(fileName, fileFormat)


        return False



if __name__ == ' __main__':


    import sys


    app = QtGui.QApplication(sys.argv)

    window = MainWindow()

    window.show()

    app.exec_()

[/code]

(jmmzon) #3

Cóż, dzięki za pomoc, trochę już "ogarnełem". Mam jednak jedno pytanie. O ile narysuje już kwadrat, elipsę itp. ale nie wiem jak wypełnić obszar już narysowanej figury. Czyli tak jak w paincie, rysuję, rysuję, rysuję, a następnie wypełniam obszary kolorami. Jak się za to zabrać?


(Copycona) #4

Pobierasz próbkę koloru z punktu, gdzie chcesz zacząć wypełniać - znaczy wspomniany punkt leży w figurze. Następnie sprawdzasz we wszystkich czterech kierunkach następny piksel, czy kolor który tam się znajduje odpowiada pobranej próbce, jeżeli tak zmieniasz jego kolor, na ten, którym chcesz wypełnić. I tak rekurencyjnie, aż się wypełnią się wszystkie punkty. Należy też pamiętać, by sprawdzić, czy przypadkiem nie skończył się obraz. Przerywasz dane wywołanie (lub: nie wywołujesz następnego, w zależności jak napisane) jeżeli te współrzędne wyszły poza obraz już, lub pobrany kolor różni się od tego przechowywanego w próbce.

Taki przykład "tekstowy" w c++ z planszą 10x10.

#include

(Linux_to_syf^^) #5

To dobry przykład jak nie należy tego robić :wink:. W C++ to przejdzie (aczkolwiek jest to nadal kiepski sposób) w wielu innych językach dbających o niegwałcenie stosu już nie (np. Java).

Artykuł w wiki:

Najprościej wrzucać wszystkie punkty w obecnym kolorze w jednej linii (dajmy na to poziomej) do listy i je zakolorowywać. Następnie dla każdego punktu z listy powtarzać proces tym razem poruszając się po "przeciwnych" liniach (dajmy na to pionowych). Algorytm trwa dopóki w liście znajdują się jakieś punkty.


(jmmzon) #6

Mam pytanie czy ten algorytm (znaleziony na necie i dostosowany do programu) jest dobry? Tzn w miarę szybki i mało pamięciożerny?:

void drawWidget::scanlineFloodFilling(int x, int y, QRgb newColor, QRgb oldColor) {

    int h = widgetImage.height();

    int w = widgetImage.width();

    if(oldColor == newColor) return;

    if(widgetImage.pixel(x,y) == newColor) return;

    int y1;

    //draw scanline

    y1 = y;

    while (y1 < h && widgetImage.pixel(x,y1) == oldColor) {

        widgetImage.setPixel(x,y1,newColor);

        y1++;

    }

    //draw new scanline

    y1 = y-1;

    while (y1 >= 0 && widgetImage.pixel(x,y1) == oldColor) {

        widgetImage.setPixel(x,y1,newColor);

        y1--;

    }


    //left

    y1=y;

    while(y1 < h && widgetImage.pixel(x,y1) == newColor) {

        if(x> 0 && widgetImage.pixel(x - 1,y1) == oldColor) {

            scanlineFloodFilling(x-1, y1, newColor, oldColor);

        }

        y1++;

    }


    y1 = y - 1;


    while(y1 >= 0 && widgetImage.pixel(x,y1) == newColor) {

        if(x> 0 && widgetImage.pixel(x - 1,y1) == oldColor) {

            scanlineFloodFilling(x-1, y1, newColor, oldColor);

        }

        y1--;

    }

    //right

    y1=y;

    while(y1 < h && widgetImage.pixel(x,y1) == newColor) {

        if(x < w - 1 && widgetImage.pixel(x + 1,y1) == oldColor) {

            scanlineFloodFilling(x+1, y1, newColor, oldColor);

        }

        y1++;

    }


    y1 = y - 1;


    while(y1 >= 0 && widgetImage.pixel(x,y1) == newColor) {

        if(x < w -1 && widgetImage.pixel(x + 1,y1) == oldColor) {

            scanlineFloodFilling(x+1, y1, newColor, oldColor);

        }

        y1--;

    }

}

Jeżeli tak, to mam problem. Otóż gdy narysuje figurę o kolorze np niebieskim, ( wewnątrz i na zewnątrz jest kolor biały), to gdy wypełnię ja tymże kolorem to cały obraz jest zamalowywany. Nie wiem jak sobie z tym problemem poradzić.

[EDIT]

Problemu nie ma jeżeli w QPainter by ustawiony tryb Antialiasingu) ale to mnie nie zadowala gdyż wynikają z tego inne problemy.