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.
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]
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ć?
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
To dobry przykład jak nie należy tego robić ;). 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:
http://en.wikipedia.org/wiki/Flood_fill
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.
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.