def read_and_convert_using_qt_to_toif(handler, filename, hs_cols, hs_rows): img = QImage(filename) if img.isNull(): handler.show_error( _('Could not load the image {} -- unknown format or other error' ).format(os.path.basename(filename))) return if (img.width(), img.height()) != ( hs_cols, hs_rows): # do we need to scale it ? img = img.scaled( hs_cols, hs_rows, Qt.IgnoreAspectRatio, Qt.SmoothTransformation ) # force to our dest size. Note that IgnoreAspectRatio guarantess the right size. Ther other modes don't if img.isNull() or (img.width(), img.height()) != ( hs_cols, hs_rows): handler.show_error( _("Could not scale image to {} x {} pixels"). format(hs_cols, hs_rows)) return target_fmt = QImage.Format_RGB888 img = img.convertToFormat( QImage.Format_Indexed8 ).convertToFormat( target_fmt ) # dither it down to 256 colors to reduce image complexity then back up to 24 bit for easy reading if img.isNull(): handler.show_error( _("Could not dither or re-render image")) return def qimg_to_toif(img, handler): try: import struct, zlib except ImportError as e: handler.show_error( _("Could not convert image, a required library is missing: {}" ).format(e)) return data, pixeldata = bytearray(), bytearray() data += b'TOIf' for y in range(img.width()): for x in range(img.height()): rgb = img.pixel(x, y) r, g, b = qRed(rgb), qGreen(rgb), qBlue(rgb) c = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ( (b & 0xF8) >> 3) pixeldata += struct.pack(">H", c) z = zlib.compressobj(level=9, wbits=10) zdata = z.compress(bytes(pixeldata)) + z.flush() zdata = zdata[2:-4] # strip header and checksum data += struct.pack("<HH", img.width(), img.height()) data += struct.pack("<I", len(zdata)) data += zdata return bytes(data) return qimg_to_toif(img, handler)
def read_and_convert_using_qt_to_raw_mono( handler, filename, hs_cols, hs_rows, invert=True): img = QImage(filename) if img.isNull(): handler.show_error( _('Could not load the image {} -- unknown format or other error' ).format(os.path.basename(filename))) return if (img.width(), img.height()) != ( hs_cols, hs_rows): # do we need to scale it ? img = img.scaled( hs_cols, hs_rows, Qt.IgnoreAspectRatio, Qt.SmoothTransformation ) # force to our dest size. Note that IgnoreAspectRatio guarantess the right size. Ther other modes don't if img.isNull() or (img.width(), img.height()) != ( hs_cols, hs_rows): handler.show_error( _("Could not scale image to {} x {} pixels"). format(hs_cols, hs_rows)) return bm = QBitmap.fromImage( img, Qt.MonoOnly) # ensures 1bpp, dithers any colors if bm.isNull(): handler.show_error( _('Could not convert image to monochrome')) return target_fmt = QImage.Format_Mono img = bm.toImage().convertToFormat( target_fmt, Qt.MonoOnly | Qt.ThresholdDither | Qt.AvoidDither ) # ensures MSB bytes again (above steps may have twiddled the bytes) lineSzOut = hs_cols // 8 # bits -> num bytes per line bimg = bytearray(hs_rows * lineSzOut) # 1024 bytes for a 128x64 img bpl = img.bytesPerLine() if bpl < lineSzOut: handler.show_error( _("Internal error converting image")) return # read in 1 scan line at a time since the scan lines may be > our target packed image for row in range(hs_rows): # copy image scanlines 1 line at a time to destination buffer ucharptr = img.constScanLine( row) # returned type is basically void* ucharptr.setsize( bpl) # inform python how big this C array is b = bytes(ucharptr) # aaand.. work with bytes. begin = row * lineSzOut end = begin + lineSzOut bimg[begin:end] = b[0:lineSzOut] if invert: for i in range(begin, end): bimg[i] = ~bimg[i] & 0xff # invert b/w return bytes(bimg)
class ImageViewer(QMainWindow): def __init__(self): super(ImageViewer, self).__init__() self.myimage = QImage() self.filename = "" self.scaleFactor = 1.778 #0.56 self.imageLabel = QLabel() self.imageLabel.setBackgroundRole(QPalette.Base) mp = QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.imageLabel.setSizePolicy(mp) self.imageLabel.setScaledContents(True) self.setCentralWidget(self.imageLabel) self.setWindowTitle("Image Viewer") w = 400 h = int(400 / self.scaleFactor) self.resize(w, h) self.move(0, 0) def resizeEvent(self, event): if not self.myimage.isNull(): self.updateView() def updateView(self): if self.scaleFactor < 1: self.imageLabel.resize(self.height() * self.scaleFactor, self.height()) else: self.imageLabel.resize(self.width(), (self.width() / self.scaleFactor)) w = self.imageLabel.width() h = self.imageLabel.height() self.resize(w, h) def loadFile(self, fileName): if self.filename: self.myimage = QImage(self.filename) if self.myimage.isNull(): QMessageBox.information(self, "Image Viewer", "Cannot load %s." % self.filename) return self.imageLabel.setPixmap(QPixmap.fromImage(self.myimage)) self.scaleFactor = int(self.myimage.width()) / int(self.myimage.height()) f = round(self.scaleFactor, 3) if self.scaleFactor < 1: self.resize(600 * self.scaleFactor, 600) else: self.resize(600, 600 / self.scaleFactor) self.setWindowTitle(os.path.splitext(str(self.filename))[0].split("/")[-1])
def open_image(self, fileName): # fileName, _ = QFileDialog.getOpenFileName(self, "Open File", # QDir.currentPath()) if fileName: image = QImage(fileName) if image.isNull(): QMessageBox.information(self, "Image Viewer", "Cannot load %s." % fileName) return qpix = QPixmap.fromImage(image) self.imageLabel.setPixmap(qpix) self.image_obj = qpix # myScaledPixmap = myPixmap.scaled(self.label.size(), Qt.KeepAspectRatio) self.scaleFactor = 1.0 # myPixmap = QPixmap(fileName) # myScaledPixmap = myPixmap.scaled(self.imageLabel.size(), Qt.KeepAspectRatio) # self.imageLabel.setPixmap(myScaledPixmap) self.printAct.setEnabled(True) self.fitToWindowAct.setEnabled(True) self.updateActions() if not self.fitToWindowAct.isChecked(): self.imageLabel.adjustSize() self.change_image_size()
def update_image_info(self, file_name): tmp_image = QImage(file_name) if tmp_image.isNull(): return short_name = file_name.split("/")[-1] self.name_lbl.setText("Name: " + short_name) img_format = short_name.split(".")[-1] self.format_lbl.setText("Format: " + img_format + " image") self.size_lbl.setText("Size: " + str(int(os.path.getsize(file_name) / 1024)) + "kB") self.height_lbl.setText("Height: " + str(tmp_image.height())) self.width_lbl.setText("Width: " + str(tmp_image.width())) self.modified_time_lbl.setText("Created: " + time.ctime(os.path.getmtime(file_name))) self.created_time_lbl.setText("Modified: " + time.ctime(os.path.getatime(file_name))) self.color_space_lbl.setText("Color space: RGB") self.color_profile_lbl.setText("Color profile: sRGB IEC61966-2.1") self.path_lbl.setText( "Where: Pictures/" + file_name.split("Photos_Library_photoslibrary/")[-1]) icon_name = "./thumbnails/" + short_name.replace(".", "@2x.") self.generate_icon(file_name, icon_name) self.icon_pixmap = QPixmap(icon_name) self.icon_pixmap = self.icon_pixmap.scaledToHeight(250) self.icon_pixmap = self.icon_pixmap.scaledToWidth(250) self.image_icon_lbl.setPixmap(self.icon_pixmap) self.image_icon_lbl.setFixedWidth(140)
def open(self): fileName, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath()) if fileName: image = QImage(fileName) if image.isNull(): QMessageBox.information(self, "Image Viewer", "Cannot load %s." % fileName) return # myPixmap = QPixmap.fromImage(image) qpix = QPixmap.fromImage(image) self.image_obj = qpix self.flag = 1 print(self.height/qpix.size().height()) # self.scaleImage(0.8) self.imageLabel.setPixmap(qpix) self.scaleFactor = 1.0 self.printAct.setEnabled(True) self.fitToWindowAct.setEnabled(True) self.updateActions() if not self.fitToWindowAct.isChecked(): self.imageLabel.adjustSize() self.change_image_size()
def main(): USAGE = 'Usage:\n python tempfile2clipboard.py [--delete] image_filepath\n' if len(sys.argv) > 1 and sys.argv[1] == '--delete': delete = True del sys.argv[1] else: delete = False if len(sys.argv) != 2: sys.stderr.write("Invalid arguments.\n" + USAGE) sys.exit(1) image_file = sys.argv[1] image = QImage(image_file) if delete: os.unlink(image_file) if image.isNull(): sys.stderr.write("Invalid image file: {}.\n".format(image_file) + USAGE) sys.exit(1) app = QApplication([]) app.clipboard().setImage(image) # Keep running until the clipboard contents change to something else: app.clipboard().dataChanged.connect(app.quit) app.exec_()
def loadFile(self, fname): #print(fname) if fname is None: action = self.sender() if isinstance(action, QAction): fname = str(action.data().toString) if not self.okToContinue(): return else: return if fname: self.filename = None image = QImage(fname) if image.isNull(): message = 'failed to read {0}'.format(fname) else: self.addRecentFiles(fname) self.image = QImage() for action, check in self.resettableActions: action.setChecked(check) self.image = image self.filename = fname self.showImage() self.dirty = False self.sizeLabel.setText('{0} x {1}'.format(image.width(), image.height())) message = 'Loaded {0}'.format(os.path.basename(fname)) self.updateStatus(message)
def _parse_image( self, width: int, height: int, bytes_per_line: int, has_alpha: bool, bits_per_color: int, channel_count: int, data: QByteArray, ) -> QImage: """Make sure the given image data is valid and return a QImage.""" # Chromium limit? assert 0 < width <= 320 assert 0 < height <= 320 # Based on dunst: # https://github.com/dunst-project/dunst/blob/v1.6.1/src/icon.c#L336-L348 # (A+7)/8 rounds up A to the next byte boundary pixelstride = (channel_count * bits_per_color + 7) // 8 expected_len = (height - 1) * bytes_per_line + width * pixelstride assert len(data) == expected_len assert bits_per_color == 8 assert channel_count == (4 if has_alpha else 3) assert bytes_per_line >= width * channel_count qimage_format = QImage.Format_RGBA8888 if has_alpha else QImage.Format_RGB888 img = QImage(data, width, height, bytes_per_line, qimage_format) assert not img.isNull() assert img.width() == width assert img.height() == height return img
def insertImage(self): # Get image file name filename = QFileDialog.getOpenFileName( self, 'Insert image', ".", "Images (*.png *.xpm *.jpg *.bmp *.gif)")[0] if filename: # Create image object image = QImage(filename) # Error if unloadable if image.isNull(): popup = QMessageBox(QMessageBox.Critical, "Image load error", "Could not load image file!", QMessageBox.Ok, self) popup.show() else: cursor = self.textCursor() cursor.insertImage(image, filename)
def loadRecentDocuments(self): self.recentDocuments = [] recentDocumentsPaths = self.kritaInstance.recentDocuments() for path in recentDocumentsPaths: if path: thumbnail = None extension = Path(path).suffix page = None if extension == '.kra': page = zipfile.ZipFile(path, "r") thumbnail = QImage.fromData(page.read("mergedimage.png")) if thumbnail.isNull(): thumbnail = QImage.fromData(page.read("preview.png")) else: thumbnail = QImage(path) if thumbnail.isNull(): continue thumbSize = QSize(200 * self.devicePixelRatioF, 150 * self.devicePixelRatioF) if thumbnail.width() <= thumbSize.width() or thumbnail.height( ) <= thumbSize.height(): thumbnail = thumbnail.scaled(thumbSize, Qt.KeepAspectRatio, Qt.FastTransformation) else: thumbnail = thumbnail.scaled(thumbSize, Qt.KeepAspectRatio, Qt.SmoothTransformation) thumbnail.setDevicePixelRatio(self.devicePixelRatioF) self.recentDocuments.append(thumbnail) self.modelReset.emit()
def openRight(self): options = QFileDialog.Options() # fileName = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath()) fileName, _ = QFileDialog.getOpenFileName( self, 'QFileDialog.getOpenFileName()', '', 'Images (*.png *.jpeg *.jpg *.bmp *.gif)', options=options) if fileName: print(fileName) image = QImage(fileName) if image.isNull(): QMessageBox.information(self, "Image Viewer", "Cannot load %s." % fileName) return self.imageLabelRight.setPixmap(QPixmap.fromImage(image)) self.scaleFactor = 1.0 self.scrollAreaRight.setVisible(True) self.window.printRightAct.setEnabled(True) self.window.fitToWindowAct.setEnabled(True) self.updateActions() if not self.window.fitToWindowAct.isChecked(): self.imageLabelRight.adjustSize()
def changeIcon(self): icon = QIcon() for row in range(self.imagesTable.rowCount()): item0 = self.imagesTable.item(row, 0) item1 = self.imagesTable.item(row, 1) item2 = self.imagesTable.item(row, 2) if item0.checkState() == Qt.Checked: if item1.text() == "Normal": mode = QIcon.Normal elif item1.text() == "Active": mode = QIcon.Active elif item1.text() == "Disabled": mode = QIcon.Disabled else: mode = QIcon.Selected if item2.text() == "On": state = QIcon.On else: state = QIcon.Off fileName = item0.data(Qt.UserRole) image = QImage(fileName) if not image.isNull(): icon.addPixmap(QPixmap.fromImage(image), mode, state) self.previewArea.setIcon(icon)
def loadFile(self, actiontrigger=False, fname=None): if fname is None: action = self.sender() if isinstance(action, QAction): fname = str(action.data()) if not self.okToContinue(): return else: return print(fname) if fname: self.filename = None image = QImage(fname) cvimg = cv2.imread(fname) if image.isNull(): message = "Failed to read {0}".format(fname) else: self.addRecentFile(fname) self.originImage = QImage() for action, check in self.resetableActions: action.setChecked(check) self.originImage = image self.currentImage = self.originImage self.imgStack.push(self.currentImage) self.cvimg = cvimg self.cvimgstack.push(self.cvimg) self.filename = fname self.showImage() self.dirty = False self.sizeLabel.setText("{0} x {1}".format( image.width(), image.height())) message = "Loaded {0}".format(os.path.basename(fname)) self.updateStatus(message)
def open(self, url): print("IN THE OPEN FUNCTION") # options = QFileDialog.Options() # fileName = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath()) # fileName, _ = QFileDialog.getOpenFileName(self, 'QFileDialog.getOpenFileName()', '', # 'Images (*.png *.jpeg *.jpg *.bmp *.gif)', options=options) fileName = url if fileName: image = QImage(fileName) if image.isNull(): QMessageBox.information(self, "Image Viewer", "Cannot load %s." % fileName) return self.imageLabel[self.count].setPixmap(QPixmap.fromImage(image)) # map(lambda obj: obj.setPixmap(QPixmap.fromImage(image)), self.imageLabel) # map(lambda obj: self._lay.addWidget(obj), self.imageLabel) self._lay.addWidget(self.imageLabel[self.count]) self.count += 1 self.scaleFactor = 1.0 self.scrollArea.setVisible(True) self.printAct.setEnabled(True) self.fitToWindowAct.setEnabled(True) self.updateActions() if not self.fitToWindowAct.isChecked(): self.imageLabel[self.count].adjustSize()
def render_avatar_image(image: QImage, size: float): if image.isNull(): return None aspect_ratio = image.width() / image.height() if aspect_ratio > 1: width = size height = size / aspect_ratio else: width = size * aspect_ratio height = size x0 = (size - width) / 2 y0 = (size - height) / 2 path = QPainterPath() path.addEllipse(QRectF(x0, y0, width, height)) picture = QPicture() painter = QPainter(picture) painter.setRenderHint(QPainter.Antialiasing, True) pen = QPen(Qt.black, 5) pen.setStyle(Qt.SolidLine) painter.setPen(pen) painter.setClipPath(path) painter.drawImage( QRectF(x0, y0, width, height), image, ) painter.end() return picture
def open(self): options = QFileDialog.Options() self.fileName, _ = QFileDialog.getOpenFileName(MainWindow, 'QFileDialog.getOpenFileName()', '', 'Images (*.png *.jpeg *.jpg *.bmp *.gif)', options=options) if self.fileName: image = QImage(self.fileName) if image.isNull(): QMessageBox.information(self.centralwidget, "Image Viewer", "Cannot load %s." % self.fileName) return pm = QPixmap.fromImage(image) w = pm.width() h = pm.height() if (w <= self.max_w and h <= self.max_h): self.imageLabel.setPixmap(pm) else: msg = QMessageBox() msg.setWindowTitle("Image Size Error") msg.setText("Image size is too big!") x = msg.exec_() # this will show our messagebox self.scaleFactor = 1.0 self.printAct.setEnabled(True) self.fitToWindowAct.setEnabled(True) self.imageLabel.setAlignment(Qt.AlignCenter) self.updateActions() if not self.fitToWindowAct.isChecked(): self.imageLabel.adjustSize()
class ImageViewer(QScrollArea): actualSizeChanged = pyqtSignal(bool) def __init__(self, parent=None): super(ImageViewer, self).__init__(parent, alignment=Qt.AlignCenter) self._actualsize = True self._image = QImage() self.setBackgroundRole(QPalette.Dark) self.setWidget(ImageWidget(self)) def setActualSize(self, enabled=True): if enabled == self._actualsize: return self.setWidgetResizable(not enabled) if enabled and not self._image.isNull(): self.widget().resize(self._image.size()) self._actualsize = enabled self.actualSizeChanged.emit(enabled) def actualSize(self): return self._actualsize def setImage(self, image): self._image = image self._pixmap = None self._pixmapsize = None if self._actualsize: self.widget().resize(image.size()) self.widget().update() def image(self): return self._image def pixmap(self, size): """Returns (and caches) a scaled pixmap for the image.""" if self._pixmapsize == size: return self._pixmap self._pixmap = QPixmap.fromImage( self._image.scaled(size, Qt.KeepAspectRatio, Qt.SmoothTransformation)) self._pixmapsize = size return self._pixmap def startDrag(self): image = self.image() data = QMimeData() data.setImageData(image) drag = QDrag(self) drag.setMimeData(data) if max(image.width(), image.height()) > 256: image = image.scaled(QSize(256, 256), Qt.KeepAspectRatio, Qt.SmoothTransformation) p = QPainter() p.begin(image) p.setCompositionMode(QPainter.CompositionMode_DestinationIn) p.fillRect(image.rect(), QColor(0, 0, 0, 160)) p.end() pixmap = QPixmap.fromImage(image) drag.setPixmap(pixmap) drag.setHotSpot(pixmap.rect().center()) drag.exec_(Qt.CopyAction)
def run(self): img = QImage(self.file_name) if img.isNull(): return img = img.scaled(self.width, self.height) self.signals.about_image.emit(self.file_name, img)
def get_screenshot( self, *, probe_pos: QPoint = None, probe_color: QColor = testutils.Color(0, 0, 0), ) -> QImage: """Get a screenshot of the current page. Arguments: probe: If given, only continue if the pixel at the given position isn't black (or whatever is specified by probe_color). """ for _ in range(5): tmp_path = self.request.getfixturevalue('tmp_path') path = tmp_path / 'screenshot.png' self.send_cmd(f':screenshot --force {path}') self.wait_for(message=f'Screenshot saved to {path}') img = QImage(str(path)) assert not img.isNull() if probe_pos is None: return img probed_color = testutils.Color(img.pixelColor(probe_pos)) if probed_color == probe_color: return img # Rendering might not be completed yet... time.sleep(0.5) raise ValueError( f"Pixel probing for {probe_color} failed (got {probed_color} on last try)" )
def setChannel(self, channel, switch=False): """ Set the image channel to visualize. If the channel has not been previously loaded it is loaded and cached. """ if self.image is None: raise ("Image has not been previously set in ViewerPlus") self.channel = channel if channel.qimage is not None: img = channel.qimage else: QApplication.setOverrideCursor(Qt.WaitCursor) img = channel.loadData() QApplication.restoreOverrideCursor() if img.isNull(): (channel.filename, filter) = QFileDialog.getOpenFileName( self, "Couldn't find the map, please select it:", QFileInfo(channel.filename).dir().path(), "Image Files (*.png *.jpg)") dir = QDir(os.getcwd()) self.map_image_filename = dir.relativeFilePath(channel.filename) img = QImage(channel.filename) if img.isNull(): raise Exception("Could not load or find the image: " + channel.filename) if switch: self.setChannelImg(img, self.zoom_factor) else: self.setChannelImg(img)
def changeIcon(self): icon = QIcon() for row in range(self.imagesTable.rowCount()): item0 = self.imagesTable.item(row, 0) item1 = self.imagesTable.item(row, 1) item2 = self.imagesTable.item(row, 2) if item0.checkState() == Qt.Checked: if item1.text() == "Normal": mode = QIcon.Normal elif item1.text() == "Active": mode = QIcon.Active elif item1.text() == "Disabled": mode = QIcon.Disabled else: mode = QIcon.Selected if item2.text() == "On": state = QIcon.On else: state = QIcon.Off fileName = item0.data(Qt.UserRole) image = QImage(fileName) if not image.isNull(): icon.addPixmap(QPixmap.fromImage(image), mode, state) self.previewArea.setIcon(icon)
def abrirImagen(self): self.ruta, _ = QFileDialog.getOpenFileName( MainWindow, "Abrir imagen", QDir.currentPath(), "Image Files (*.png *.jpg *.bmp *.tif)") url = QUrl.fromLocalFile(self.ruta) if self.ruta: image = QImage(self.ruta) imageP = Image.open(self.ruta) if image.isNull(): # En caso de error mostrar un mensaje grafico QMessageBox.information( self, "Visualizador de imagenes", "No se pudo cargar la imagen %s" % (self.ruta)) return self.painter.setPixmap(QPixmap.fromImage(image)) self.painter.setAlignment(Qt.Qt.AlignCenter) self.label_size.setText("Tamaño: %d x %d" % (image.width(), image.height())) self.label_nombre.setText("Nombre: %s " % (url.fileName())) self.label_tipo.setText("Tipo: %s" % (imageP.mode)) MainWindow.setWindowTitle( "Editor de imagenes - %s" % (url.fileName()) ) # Agregamos el nombre de la imagen al titulo de la imagen self.spinAlto.setValue(image.height()) self.spinAncho.setValue(image.width())
def get_avatar(self, uid, avatar_url): try: req = requests.get(avatar_url) if req.status_code == 200: imgformat = req.headers.get( 'content-type', 'image/jpg').split('/')[1] Constants.ImageAvatar = os.path.join( Constants.ImageDir, str(uid)).replace('\\', '/') + '.jpg' AppLog.debug('image type: {}'.format(imgformat)) AppLog.debug( 'content length: {}'.format(len(req.content))) image = QImage() if image.loadFromData(req.content): # 缩放图片 if not image.isNull(): image = image.scaled(130, 130, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) AppLog.debug('save to: {}'.format( Constants.ImageAvatar)) image.save(Constants.ImageAvatar) else: AppLog.warn('avatar image is null') else: AppLog.warn('can not load from image data') except Exception as e: AppLog.exception(e)
def open(self): fileName, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath()) if fileName: image = QImage(fileName) if image.isNull(): QMessageBox.information(self, "Image Viewer", "Cannot load %s." % fileName) return self.imageLabel.setPixmap(QPixmap.fromImage(image)) self.scaleFactor = 1.0 ratio = np.max([self.imageLabel.pixmap().width()/w, self.imageLabel.pixmap().height()/h]) self.minRatio = 1/ratio self.maxRatio = 1.0 self.scaleImage(self.minRatio) self.printAct.setEnabled(True) self.fitToWindowAct.setEnabled(True) self.updateActions() if not self.fitToWindowAct.isChecked(): self.imageLabel.adjustSize() self.scaleImage(1)
def mouseReleaseEvent(self, event): if self.selection.isVisible(): currentQRect = self.selection.geometry() cropQPixmap = self.pixmap().copy(currentQRect) cropQPixmap.save('output.png') image = QImage("output.png") if image.isNull(): QMessageBox.information(self.centralwidget, "Image Viewer", "Cannot load %s." % self.fileName) return pm = QPixmap.fromImage(image) h = pm.height() w = pm.width() if (h > w): self._parent_class.originalImageLabel.setPixmap(pm.scaledToHeight(400)) else: self._parent_class.originalImageLabel.setPixmap(pm.scaledToWidth(400)) self.selection.hide()
def _loadQtImage(path: str) -> QImage: reader = QImageReader(path) image = QImage(reader.read()) if not image.isNull(): return image else: return None
def open(self): fileName, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath()) if fileName: # fileName = 'data/soojung/1513023157032.jpg' # cv2.getTextSize('myungeun : 0.3513', cv2.FONT_HERSHEY_SIMPLEX, 0.5,1) # cv2.rectangle(bgrImage, (dx+w, y), (dx+w+94, yo+3), (255,255,255), -1) outimg = self.findFaceAndClassify(fileName) outimg = cv2.cvtColor(outimg, cv2.COLOR_BGR2RGB) h, w, c = outimg.shape bytesPerLine = 3 * w image = QImage(outimg, w, h, bytesPerLine, QImage.Format_RGB888) if image.isNull(): QMessageBox.information(self, "Image Viewer", "Cannot load %s." % fileName) return self.imageLabel.setPixmap(QPixmap.fromImage(image)) self.scaleFactor = 1.0 self.fitToWindowAct.setEnabled(True) self.updateActions() self.resize(w, h) if not self.fitToWindowAct.isChecked(): self.imageLabel.adjustSize()
def view(self): fileNameDir = "Data/1999_2017_landsat_time_series/roi/cut/fileNames.txt" if 'fileNameDir' in locals(): pass else: raise AssertionError("FILE_NAME_DIR is not given!") with open(fileNameDir, "r") as file: imageFileNames = file.readlines() # imageFiles is a list file.close() for fileName in imageFileNames: fileName = "Data/1999_2017_landsat_time_series/roi/cut/" + fileName[: -1] + ".tif" image = QImage(fileName) if image.isNull(): QMessageBox.information(self, "Image Viewer", "Cannot load %s." % fileName) return self.imageLabel.setPixmap(QPixmap.fromImage(image)) self.scaleFactor = 1.0 #self.printAct.setEnabled(True) self.fitToWindowAct.setEnabled(True) self.updateActions() if not self.fitToWindowAct.isChecked(): self.imageLabel.adjustSize() app.processEvents() time.sleep(0.001) """
def _get_hints_arg(self, *, origin_url: QUrl, icon: QImage) -> Dict[str, Any]: """Get the hints argument for present().""" origin_url_str = origin_url.toDisplayString() hints: Dict[str, Any] = { # Include the origin in case the user wants to do different things # with different origin's notifications. "x-qutebrowser-origin": origin_url_str, "desktop-entry": "org.qutebrowser.qutebrowser", } is_useful_origin = self._should_include_origin(origin_url) if self._capabilities.kde_origin_name and is_useful_origin: hints["x-kde-origin-name"] = origin_url_str if icon.isNull(): filename = 'icons/qutebrowser-64x64.png' icon = QImage.fromData(resources.read_file_binary(filename)) key = self._quirks.icon_key or "image-data" data = self._convert_image(icon) if data is not None: hints[key] = data return hints
def on_imagePicker_textChanged(self, address): """ Private slot handling changes of the image path. @param address image address (URL or local path) @type str """ if address and "://" not in address: image = QImage(address) # load the file to set the size spin boxes if image.isNull(): self.widthSpinBox.setValue(0) self.heightSpinBox.setValue(0) self.__originalImageSize = QSize() self.__aspectRatio = 1 else: self.widthSpinBox.setValue(image.width()) self.heightSpinBox.setValue(image.height()) self.__originalImageSize = image.size() self.__aspectRatio = ( float(self.__originalImageSize.height()) / self.__originalImageSize.width()) else: self.widthSpinBox.setValue(0) self.heightSpinBox.setValue(0) self.__originalImageSize = QSize() self.__aspectRatio = 1 self.__updateOkButton()
def open(self): #if self.maybeSave(): fileName, _ = QFileDialog.getOpenFileName(self, "Open Image", QDir.currentPath()) if fileName: image = QImage(fileName) if image.isNull(): QMessageBox.information(self, "iScissor", "Cannot load %s." % fileName) return self.setCurrentFile(fileName) imageLabel = proj1_image.Image(fileName) #QLabel() imageLabel.setBackgroundRole(QPalette.Base) imageLabel.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) imageLabel.setScaledContents(True) imageLabel.setPixmap(QPixmap.fromImage(image)) self.graphicsView.setWidget(imageLabel) self.graphicsView.initForImage(fileName) self.scaleFactor = 1.0 self.normalSize() self.iScissorStartAct.setEnabled(True) self.undoAct.setEnabled(True) self.normalSizeAct.setEnabled(True) self.zoomInAct.setEnabled(True) self.zoomOutAct.setEnabled(True) print(self.getCurrentFile()) return
def createImage(self, transform): original = QImage(self.image) if original.isNull(): return original size = transform.map(QPoint(self.maxWidth, self.maxHeight)) w = size.x() h = size.y() # Optimization: if image is smaller than maximum allowed size, just # return the loaded image. if original.size().height() <= h and original.size().width() <= w and not self.adjustSize and self.scale == 1: return original # Calculate what the size of the final image will be. w = min(w, float(original.size().width()) * self.scale) h = min(h, float(original.size().height()) * self.scale) adjustx = 1.0 adjusty = 1.0 if self.adjustSize: adjustx = min(transform.m11(), transform.m22()) adjusty = max(transform.m22(), adjustx) w *= adjustx h *= adjusty # Create a new image with correct size, and draw original on it. image = QImage(int(w + 2), int(h + 2), QImage.Format_ARGB32_Premultiplied) image.fill(QColor(0, 0, 0, 0).rgba()) painter = QPainter(image) painter.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) if self.adjustSize: painter.scale(adjustx, adjusty) if self.scale != 1: painter.scale(self.scale, self.scale) painter.drawImage(0, 0, original) if not self.adjustSize: # Blur out edges. blur = 30 if h < original.height(): brush1 = QLinearGradient(0, h - blur, 0, h) brush1.setSpread(QLinearGradient.PadSpread) brush1.setColorAt(0.0, QColor(0, 0, 0, 0)) brush1.setColorAt(1.0, Colors.sceneBg1) painter.fillRect(0, int(h) - blur, original.width(), int(h), brush1) if w < original.width(): brush2 = QLinearGradient(w - blur, 0, w, 0) brush2.setSpread(QLinearGradient.PadSpread) brush2.setColorAt(0.0, QColor(0, 0, 0, 0)) brush2.setColorAt(1.0, Colors.sceneBg1) painter.fillRect(int(w) - blur, 0, int(w), original.height(), brush2) return image
def setName(self, imageName): '''Sets the file name of the image. Either an absolute path name or a path relative to the current working directory.''' self.imageName = imageName img = QImage(imageName) if not img.isNull(): self.bufferedImage = img
def setName(self, imageName): '''Sets the file name of the image. Either an absolute path name or a path relative to the current working directory.''' self.imageName = imageName img = QImage(imageName) if not img.isNull(): self.bufferedImage = img
def insertImage(self): filePath = QFileDialog.getOpenFileName(self, caption='Select Image file')[0] # print('{}: "{}" {}'.format(type(filePath), filePath, len(filePath))) if len(filePath) > 0: image = QImage(filePath) if image.isNull(): QMessageBox.information(self, "Error", '"{}"\nis not a valid image file'.format(filePath)) else: self.editView.insertImage(image)
def _task_load_thumbnail(ppath, thumb_size, on_method=None, **kwargs): if ppath: img = QImage(ppath) if not img.isNull(): size = img.size() if size.width() != thumb_size[0]: # TODO: use _task_thumbnail img = _rounded_qimage(img.scaled(thumb_size[0], thumb_size[1], Qt.KeepAspectRatio, Qt.SmoothTransformation), 5) if on_method: on_method(img, **kwargs) return img
def useDefaultBackground(self): filename = "background.png" if not os.path.exists(filename): filename = os.path.join(os.path.dirname(__file__), filename) if os.path.exists(filename): image = QImage(filename) if not image.isNull(): self._makeBackground(image) moveToCenter(self) self.canvas.positWidgets() self.update() settings = self.platform.getSettings() settings.remove("background")
def get_info(self, path): from PyQt5.QtGui import QImage img = QImage(path) if img.isNull(): return try: return { 'mtime': int(float(img.text(KEY_MTIME) or 0)), 'uri': img.text(KEY_URI), } except ValueError: return
def update_metadata(self, dest, moreinfo=None): from PyQt5.QtGui import QImage img = QImage(dest) if img.isNull(): return self.setattributes(img, moreinfo) img.setText(KEY_WIDTH, str(img.size().width())) img.setText(KEY_HEIGHT, str(img.size().height())) tmp = _mkstemp(dest) img.save(tmp) os.rename(tmp, dest) return dest
def loadImage(self, fileName): image = QImage(fileName) assert not image.isNull() pixmap = QPixmap.fromImage(image) mask = pixmap.createMaskFromColor(QColor(image.pixel(0,0)), Qt.MaskInColor) if self.use_title_bar: pixmap.setMask(mask) self.imageLabel.setMask(mask) else: self.setMask(mask) self.imageLabel.setPixmap(pixmap) self.imageLabel.adjustSize() self.setFixedSize(image.size()) self.landOnScreen()
def open(self): fileName, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath()) if fileName: image = QImage(fileName) if image.isNull(): QMessageBox.information(self, "Image Viewer", "Cannot load %s." % fileName) return self.imageLabel.setPixmap(QPixmap.fromImage(image)) self.scaleFactor = 1.0 self.printAct.setEnabled(True) self.fitToWindowAct.setEnabled(True) self.updateActions() if not self.fitToWindowAct.isChecked(): self.imageLabel.adjustSize()
def _task_thumbnail(gallery_or_path, img=None, width=app_constants.THUMB_W_SIZE, height=app_constants.THUMB_H_SIZE): """ """ log_i("Generating thumbnail") # generate a cache dir if required if not os.path.isdir(db_constants.THUMBNAIL_PATH): os.mkdir(db_constants.THUMBNAIL_PATH) try: if not img: img_path = utils.get_gallery_img(gallery_or_path) else: img_path = img if not img_path: raise IndexError for ext in utils.IMG_FILES: if img_path.lower().endswith(ext): suff = ext # the image ext with dot # generate unique file name file_name = str(uuid.uuid4()) + ".png" new_img_path = os.path.join(db_constants.THUMBNAIL_PATH, (file_name)) if not os.path.isfile(img_path): raise IndexError # Do the scaling try: im_data = utils.PToQImageHelper(img_path) image = QImage(im_data['data'], im_data['im'].size[0], im_data['im'].size[1], im_data['format']) if im_data['colortable']: image.setColorTable(im_data['colortable']) except ValueError: image = QImage() image.load(img_path) if image.isNull(): raise IndexError radius = 5 image = image.scaled(width, height, Qt.KeepAspectRatio, Qt.SmoothTransformation) r_image = _rounded_qimage(image, radius) r_image.save(new_img_path, "PNG", quality=80) except IndexError: new_img_path = app_constants.NO_IMAGE_PATH return new_img_path
def changeFileIcon(self): "用户点击了更换图标按钮。" filename, selectedFilter = QFileDialog.getOpenFileName(self, self.windowTitle()) if not filename: return image = QImage(filename) if not image.isNull(): self.shortcutIcon = QIcon(QPixmap.fromImage(image)) else: ip = QFileIconProvider() shortcutIcon = ip.icon(QFileInfo(filename)) if shortcutIcon.isNull(): QMessageBox.information(self, self.tr("更换图标"), self.tr("您选择的文件不包含任何可以使用的图标。")) return self.shortcutIcon = shortcutIcon self.iconPath = filename self.btnFace.setIcon(self.shortcutIcon)
def show_image(self): """docstring for show_image""" if self.output_path: image = QImage(self.output_path) if image.isNull(): QMessageBox.information(self, "Image Viewer", "Cannot load %s." % self.output_path) return self.imageLabel.setPixmap(QPixmap.fromImage(image)) self.scaleFactor = 1.0 self.printAct.setEnabled(True) self.fitToWindowAct.setEnabled(True) self.updateActions() if not self.fitToWindowAct.isChecked(): self.imageLabel.adjustSize()
def create_thumbnail(self, src, dest, size): from PyQt5.QtCore import Qt from PyQt5.QtGui import QImage img = QImage(str(src)) if img.isNull(): return res = { KEY_MTIME: _any2mtime(src), KEY_WIDTH: img.width(), KEY_HEIGHT: img.height(), } img = img.scaled(size, size, Qt.KeepAspectRatio, Qt.SmoothTransformation) img.save(dest) return res
def gen_thumbnail(gallery, width=gui_constants.THUMB_W_SIZE, height=gui_constants.THUMB_H_SIZE, img=None): """Generates a thumbnail with unique filename in the cache dir. Returns absolute path to the created thumbnail """ assert isinstance(gallery, Gallery), "gallery should be an instance of Gallery class" img_path_queue = queue.Queue() # generate a cache dir if required if not os.path.isdir(db_constants.THUMBNAIL_PATH): os.mkdir(db_constants.THUMBNAIL_PATH) try: if not img: if gallery.is_archive: img_path = get_gallery_img(gallery.chapters[0], gallery.path) else: img_path = get_gallery_img(gallery.chapters[0]) else: img_path = img if not img_path: raise IndexError for ext in IMG_FILES: if img_path.lower().endswith(ext): suff = ext # the image ext with dot # generate unique file name file_name = str(uuid.uuid4()) + suff new_img_path = os.path.join(db_constants.THUMBNAIL_PATH, (file_name)) if not os.path.isfile(img_path): raise IndexError # Do the scaling image = QImage() image.load(img_path) if image.isNull(): raise IndexError image = image.scaled(width, height, Qt.KeepAspectRatio, Qt.SmoothTransformation) image.save(new_img_path, quality=100) except IndexError: new_img_path = gui_constants.NO_IMAGE_PATH abs_path = os.path.abspath(new_img_path) return abs_path
def __showPixmap(self, filename): """ Private method to show a file. @param filename name of the file to be shown (string) @return flag indicating success (boolean) """ image = QImage(filename) if image.isNull(): E5MessageBox.warning( self, self.tr("Pixmap-Viewer"), self.tr( """<p>The file <b>{0}</b> cannot be displayed.""" """ The format is not supported.</p>""").format(filename)) return False self.pixmapLabel.setPixmap(QPixmap.fromImage(image)) self.pixmapLabel.adjustSize() return True
def qimage_of_bgr(bgr): """ A QImage representation of a BGR numpy array """ import cv2 import numpy as np bgr = cv2.cvtColor(bgr.astype('uint8'), cv2.COLOR_BGR2RGB) bgr = np.ascontiguousarray(bgr) qt_image = QImage( bgr.data, bgr.shape[1], bgr.shape[0], bgr.strides[0], QImage.Format_RGB888 ) if qt_image.isNull(): raise ValueError('Unable to create QImage') else: # QImage does not take a deep copy of np_arr.data so hold a reference # to it assert(not hasattr(qt_image, 'bgr_array')) qt_image.bgr_array = bgr return qt_image
def set_thumbnail_image(self, image: QImage) -> None: if image is None: self.status = ThumbnailStatus.THUMBNAIL_UNAVAILABLE self.pixmap = None else: assert not image.isNull() self.status = ThumbnailStatus.THUMBNAIL_READY self.pixmap = QPixmap(image) try: mtime_txt = image.text("Thumb::MTime") self.mtime = int(mtime_txt) # Thumb::MTime only has 1 second resolution, thus it # is quite possible for an thumbnail to be out of date # when the file was modified while the thumbnail was # extracting (e.g. looking at an extracting archive). if int(self.file_item.fileinfo.mtime()) != self.mtime: self.reset() except ValueError as err: logger.error("%s: couldn't read Thumb::MTime tag on thumbnail: %s", self.file_item.fileinfo.location(), err)
def preRead(self, file_name, *args, **kwargs): img = QImage(file_name) if img.isNull(): Logger.log("e", "Image is corrupt.") return MeshReader.PreReadResult.failed width = img.width() depth = img.height() largest = max(width, depth) width = width / largest * self._ui.default_width depth = depth / largest * self._ui.default_depth self._ui.setWidthAndDepth(width, depth) self._ui.showConfigUI() self._ui.waitForUIToClose() if self._ui.getCancelled(): return MeshReader.PreReadResult.cancelled return MeshReader.PreReadResult.accepted
def changeBackground(self): filename, selectedFilter = QFileDialog.getOpenFileName(self, self.tr("Change Background"), \ QStandardPaths.writableLocation(QStandardPaths.PicturesLocation), \ self.tr("Image Files (*.png *.gif *.jpg *.jpeg *.bmp *.mng *ico)")) if not filename: return image = QImage(filename) if image.isNull(): QMessageBox.information(self, self.tr("Change Background"), \ self.tr("不能读取图像文件,请检查文件格式是否正确,或者图片是否已经损坏。")) return if image.width() < 800 or image.height() < 600: answer = QMessageBox.information(self, self.tr("Change Background"), \ self.tr("不建议设置小于800x600的图片作为背景图案。如果继续,可能会使快捷面板显示错乱。是否继续?"), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if answer == QMessageBox.No: return self._makeBackground(image) moveToCenter(self) self.canvas.positWidgets() self.update() with self.platform.getSettings() as settings: settings.setValue("background", filename)
def pix_to_qimage(leptonica, pix_image): """ Convert leptonica PIX to QT QImage """ # TODO(zdenop): 8509_001.4B.tif crash this -> implement PIX structure if not leptonica: return None width = leptonica.pixGetWidth(pix_image) height = leptonica.pixGetHeight(pix_image) depth = leptonica.pixGetDepth(pix_image) if depth == 1: image_format = QImage.Format_Mono elif depth == 8: image_format = QImage.Format_Indexed8 elif depth == 32: image_format = QImage.Format_RGB32 else: # Convert other depths to 32 pix_image = leptonica.pixConvertTo32(pix_image) image_format = QImage.Format_RGB32 bytes_per_line = leptonica.pixGetWpl(pix_image) * 4 image_datas = leptonica.pixEndianByteSwapNew(pix_image) datas = leptonica.pixGetData(image_datas) result = QImage(datas, width, height, bytes_per_line, image_format) result.setColorTable(_grayscaleCT) # (depth == 8) if depth == 1: result.setColorTable(_bwCT) if result.isNull(): none = QImage(0, 0, QImage.Format_Invalid) print('Invalid format!!!') return none return result.rgbSwapped()
query = QSqlQuery(db) query.prepare("""INSERT INTO photos (image) VALUES (?)""") ba = QtCore.QByteArray(img_data) query.addBindValue(ba) query.exec_() img_id = query.lastInsertId() coin.setValue(field_name, img_id) if (field_name == 'obverseimg' and is_obverse_enabled) or \ (field_name == 'reverseimg' and is_reverse_enabled): image.loadFromData(img_data) image = image.scaledToHeight(height, Qt.SmoothTransformation) if not image.isNull(): ba = QtCore.QByteArray() buffer = QtCore.QBuffer(ba) buffer.open(QtCore.QIODevice.WriteOnly) # Store as PNG for better view image.save(buffer, 'png') coin.setValue('image', ba) if action == 'update_desc': dest_model.insertRecord(-1, coin) coin_id += 1 for i in range(coin.count()): if coin.fieldName(i) not in ('id', 'title', 'subjectshort', 'series', 'obversevar'): coin.setNull(i) dest_model.insertRecord(-1, coin) coin_id += 1
def open_image(self, path): """Open specific image via path.""" image = QImage(str(path)) if not image.isNull(): self.model.image_path = path self.canvas.update_image(self.settings.getint('Viewport', 'selection'), image)
def exportToMobile(model, params): IMAGE_FORMAT = 'jpg' IMAGE_COMPRESS = 50 USED_FIELDS = ('title', 'unit', 'country', 'year', 'mint', 'mintmark', 'issuedate', 'type', 'series', 'subjectshort', 'material', 'fineness', 'diameter', 'thickness', 'weight', 'mintage', 'rarity', 'obverseimg', 'reverseimg', 'subject', 'price1', 'price2', 'price3', 'price4') if os.path.isfile(params['file']): os.remove(params['file']) db = QSqlDatabase.addDatabase('QSQLITE', 'mobile') db.setDatabaseName(params['file']) if not db.open(): print(db.lastError().text()) QMessageBox.critical(None, "Create mobile collection", "Can't open collection") return sql = """CREATE TABLE "android_metadata" ("locale" TEXT DEFAULT 'en_US')""" QSqlQuery(sql, db) sql = """INSERT INTO "android_metadata" VALUES ('en_US')""" QSqlQuery(sql, db) mobile_settings = {'Version': 1, 'Type': 'MobilePro', 'Filter': params['filter']} sql = """CREATE TABLE settings ( title CHAR NOT NULL UNIQUE, value CHAR)""" QSqlQuery(sql, db) for key, value in mobile_settings.items(): query = QSqlQuery(db) query.prepare("""INSERT INTO settings (title, value) VALUES (?, ?)""") query.addBindValue(key) query.addBindValue(str(value)) query.exec_() sql = """CREATE TABLE updates ( title CHAR NOT NULL UNIQUE, value CHAR)""" QSqlQuery(sql, db) sql = """INSERT INTO updates (title, value) VALUES ('160203', '2016-02-03T10:19:00')""" QSqlQuery(sql, db) sql = """CREATE TABLE photos ( id INTEGER PRIMARY KEY, image BLOB)""" QSqlQuery(sql, db) sql = """CREATE TABLE coins ( id INTEGER PRIMARY KEY, description_id INTEGER, grade INTEGER, createdat STRING)""" QSqlQuery(sql, db) sql = """CREATE INDEX coins_descriptions ON coins(description_id)""" QSqlQuery(sql, db) sqlFields = [] fields = CollectionFieldsBase() for field in fields: if field.name == 'id': sqlFields.append('id INTEGER PRIMARY KEY') elif field.name == 'image': sqlFields.append('image INTEGER') elif field.name in USED_FIELDS: sqlFields.append("%s %s" % (field.name, Type.toSql(field.type))) sql = "CREATE TABLE descriptions (" + ", ".join(sqlFields) + ")" QSqlQuery(sql, db) while model.canFetchMore(): model.fetchMore() dest_model = QSqlTableModel(None, db) dest_model.setEditStrategy(QSqlTableModel.OnManualSubmit) dest_model.setTable('descriptions') dest_model.select() height = 64 if params['density'] == 'HDPI': height *= 1.5 elif params['density'] == 'XHDPI': height *= 2 elif params['density'] == 'XXHDPI': height *= 3 elif params['density'] == 'XXXHDPI': height *= 4 maxHeight = height * 4 is_obverse_enabled = params['image'] in (ExportDialog.IMAGE_OBVERSE, ExportDialog.IMAGE_BOTH) is_reverse_enabled = params['image'] in (ExportDialog.IMAGE_REVERSE, ExportDialog.IMAGE_BOTH) fields = CollectionFieldsBase() count = model.rowCount() progressDlg = Gui.ProgressDialog("Exporting records", "Cancel", count, None) for i in range(count): progressDlg.step() if progressDlg.wasCanceled(): break coin = model.record(i) if coin.value('status') in ('pass', 'sold'): continue dest_record = dest_model.record() for field in fields: if field.name in ('id', 'image', 'obverseimg', 'reverseimg'): continue if field.name in USED_FIELDS: val = coin.value(field.name) if val is None or val == '': continue dest_record.setValue(field.name, val) # Process images is_obverse_present = not coin.isNull('obverseimg') is_reverse_present = not coin.isNull('reverseimg') if is_obverse_present or is_reverse_present: obverseImage = QImage() reverseImage = QImage() if is_obverse_present: ba = QtCore.QByteArray() buffer = QtCore.QBuffer(ba) buffer.open(QtCore.QIODevice.WriteOnly) obverseImage.loadFromData(coin.value('obverseimg')) if not obverseImage.isNull() and not params['fullimage'] and obverseImage.height() > maxHeight: scaledImage = obverseImage.scaled(maxHeight, maxHeight, Qt.KeepAspectRatio, Qt.SmoothTransformation) scaledImage.save(buffer, IMAGE_FORMAT, IMAGE_COMPRESS) save_data = ba else: if not obverseImage.isNull(): obverseImage.save(buffer, IMAGE_FORMAT, IMAGE_COMPRESS) save_data = ba else: save_data = coin.value('obverseimg') query = QSqlQuery(db) query.prepare("""INSERT INTO photos (image) VALUES (?)""") query.addBindValue(save_data) query.exec_() img_id = query.lastInsertId() dest_record.setValue('obverseimg', img_id) if not obverseImage.isNull(): obverseImage = obverseImage.scaledToHeight(height, Qt.SmoothTransformation) if is_reverse_present: ba = QtCore.QByteArray() buffer = QtCore.QBuffer(ba) buffer.open(QtCore.QIODevice.WriteOnly) reverseImage.loadFromData(coin.value('reverseimg')) if not reverseImage.isNull() and not params['fullimage'] and reverseImage.height() > maxHeight: scaledImage = reverseImage.scaled(maxHeight, maxHeight, Qt.KeepAspectRatio, Qt.SmoothTransformation) scaledImage.save(buffer, IMAGE_FORMAT, IMAGE_COMPRESS) save_data = ba else: if not reverseImage.isNull(): reverseImage.save(buffer, IMAGE_FORMAT, IMAGE_COMPRESS) save_data = ba else: save_data = coin.value('reverseimg') query = QSqlQuery(db) query.prepare("""INSERT INTO photos (image) VALUES (?)""") query.addBindValue(save_data) query.exec_() img_id = query.lastInsertId() dest_record.setValue('reverseimg', img_id) if not reverseImage.isNull(): reverseImage = reverseImage.scaledToHeight(height, Qt.SmoothTransformation) if not is_obverse_enabled: obverseImage = QImage() if not is_reverse_enabled: reverseImage = QImage() image = QImage(obverseImage.width() + reverseImage.width(), height, QImage.Format_RGB32) image.fill(QColor(Qt.white).rgb()) paint = QPainter(image) if is_obverse_present and is_obverse_enabled: paint.drawImage(QtCore.QRectF(0, 0, obverseImage.width(), height), obverseImage, QtCore.QRectF(0, 0, obverseImage.width(), height)) if is_reverse_present and is_reverse_enabled: paint.drawImage(QtCore.QRectF(obverseImage.width(), 0, reverseImage.width(), height), reverseImage, QtCore.QRectF(0, 0, reverseImage.width(), height)) paint.end() ba = QtCore.QByteArray() buffer = QtCore.QBuffer(ba) buffer.open(QtCore.QIODevice.WriteOnly) image.save(buffer, IMAGE_FORMAT, 75) query = QSqlQuery(db) query.prepare("""INSERT INTO photos (image) VALUES (?)""") query.addBindValue(ba) query.exec_() img_id = query.lastInsertId() dest_record.setValue('image', img_id) dest_model.insertRecord(-1, dest_record) progressDlg.setLabelText("Saving...") dest_model.submitAll() progressDlg.setLabelText("Compact...") QSqlQuery("""UPDATE descriptions SET reverseimg = (select t2.id from descriptions t3 join (select id, image from photos group by image having count(*) > 1) t2 on t1.image = t2.image join photos t1 on t3.reverseimg = t1.id where t1.id <> t2.id and t3.id = descriptions.id) WHERE descriptions.id in (select t3.id from descriptions t3 join (select id, image from photos group by image having count(*) > 1) t2 on t1.image = t2.image join photos t1 on t3.reverseimg = t1.id where t1.id <> t2.id) """, db) QSqlQuery("""UPDATE descriptions SET obverseimg = (select t2.id from descriptions t3 join (select id, image from photos group by image having count(*) > 1) t2 on t1.image = t2.image join photos t1 on t3.obverseimg = t1.id where t1.id <> t2.id and t3.id = descriptions.id) WHERE descriptions.id in (select t3.id from descriptions t3 join (select id, image from photos group by image having count(*) > 1) t2 on t1.image = t2.image join photos t1 on t3.obverseimg = t1.id where t1.id <> t2.id) """, db) QSqlQuery("""UPDATE descriptions SET image = (select t2.id from descriptions t3 join (select id, image from photos group by image having count(*) > 1) t2 on t1.image = t2.image join photos t1 on t3.image = t1.id where t1.id <> t2.id and t3.id = descriptions.id) WHERE descriptions.id in (select t3.id from descriptions t3 join (select id, image from photos group by image having count(*) > 1) t2 on t1.image = t2.image join photos t1 on t3.image = t1.id where t1.id <> t2.id) """, db) QSqlQuery("""DELETE FROM photos WHERE id NOT IN (SELECT id FROM photos GROUP BY image)""", db) db.close() progressDlg.setLabelText("Vacuum...") db = QSqlDatabase.addDatabase('QSQLITE', 'mobile') db.setDatabaseName(params['file']) if not db.open(): print(db.lastError().text()) QMessageBox.critical(None, "Create mobile collection", "Can't open collection") return QSqlQuery("VACUUM", db) db.close() progressDlg.reset()
def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, image_color_invert): scene_node = SceneNode() mesh = MeshBuilder() img = QImage(file_name) if img.isNull(): Logger.log("e", "Image is corrupt.") return None width = max(img.width(), 2) height = max(img.height(), 2) aspect = height / width if img.width() < 2 or img.height() < 2: img = img.scaled(width, height, Qt.IgnoreAspectRatio) base_height = max(base_height, 0) peak_height = max(peak_height, -base_height) xz_size = max(xz_size, 1) scale_vector = Vector(xz_size, peak_height, xz_size) if width > height: scale_vector = scale_vector.set(z=scale_vector.z * aspect) elif height > width: scale_vector = scale_vector.set(x=scale_vector.x / aspect) if width > max_size or height > max_size: scale_factor = max_size / width if height > width: scale_factor = max_size / height width = int(max(round(width * scale_factor), 2)) height = int(max(round(height * scale_factor), 2)) img = img.scaled(width, height, Qt.IgnoreAspectRatio) width_minus_one = width - 1 height_minus_one = height - 1 Job.yieldThread() texel_width = 1.0 / (width_minus_one) * scale_vector.x texel_height = 1.0 / (height_minus_one) * scale_vector.z height_data = numpy.zeros((height, width), dtype=numpy.float32) for x in range(0, width): for y in range(0, height): qrgb = img.pixel(x, y) avg = float(qRed(qrgb) + qGreen(qrgb) + qBlue(qrgb)) / (3 * 255) height_data[y, x] = avg Job.yieldThread() if image_color_invert: height_data = 1 - height_data for _ in range(0, blur_iterations): copy = numpy.pad(height_data, ((1, 1), (1, 1)), mode= "edge") height_data += copy[1:-1, 2:] height_data += copy[1:-1, :-2] height_data += copy[2:, 1:-1] height_data += copy[:-2, 1:-1] height_data += copy[2:, 2:] height_data += copy[:-2, 2:] height_data += copy[2:, :-2] height_data += copy[:-2, :-2] height_data /= 9 Job.yieldThread() height_data *= scale_vector.y height_data += base_height heightmap_face_count = 2 * height_minus_one * width_minus_one total_face_count = heightmap_face_count + (width_minus_one * 2) * (height_minus_one * 2) + 2 mesh.reserveFaceCount(total_face_count) # initialize to texel space vertex offsets. # 6 is for 6 vertices for each texel quad. heightmap_vertices = numpy.zeros((width_minus_one * height_minus_one, 6, 3), dtype = numpy.float32) heightmap_vertices = heightmap_vertices + numpy.array([[ [0, base_height, 0], [0, base_height, texel_height], [texel_width, base_height, texel_height], [texel_width, base_height, texel_height], [texel_width, base_height, 0], [0, base_height, 0] ]], dtype = numpy.float32) offsetsz, offsetsx = numpy.mgrid[0: height_minus_one, 0: width - 1] offsetsx = numpy.array(offsetsx, numpy.float32).reshape(-1, 1) * texel_width offsetsz = numpy.array(offsetsz, numpy.float32).reshape(-1, 1) * texel_height # offsets for each texel quad heightmap_vertex_offsets = numpy.concatenate([offsetsx, numpy.zeros((offsetsx.shape[0], offsetsx.shape[1]), dtype=numpy.float32), offsetsz], 1) heightmap_vertices += heightmap_vertex_offsets.repeat(6, 0).reshape(-1, 6, 3) # apply height data to y values heightmap_vertices[:, 0, 1] = heightmap_vertices[:, 5, 1] = height_data[:-1, :-1].reshape(-1) heightmap_vertices[:, 1, 1] = height_data[1:, :-1].reshape(-1) heightmap_vertices[:, 2, 1] = heightmap_vertices[:, 3, 1] = height_data[1:, 1:].reshape(-1) heightmap_vertices[:, 4, 1] = height_data[:-1, 1:].reshape(-1) heightmap_indices = numpy.array(numpy.mgrid[0:heightmap_face_count * 3], dtype=numpy.int32).reshape(-1, 3) mesh._vertices[0:(heightmap_vertices.size // 3), :] = heightmap_vertices.reshape(-1, 3) mesh._indices[0:(heightmap_indices.size // 3), :] = heightmap_indices mesh._vertex_count = heightmap_vertices.size // 3 mesh._face_count = heightmap_indices.size // 3 geo_width = width_minus_one * texel_width geo_height = height_minus_one * texel_height # bottom mesh.addFaceByPoints(0, 0, 0, 0, 0, geo_height, geo_width, 0, geo_height) mesh.addFaceByPoints(geo_width, 0, geo_height, geo_width, 0, 0, 0, 0, 0) # north and south walls for n in range(0, width_minus_one): x = n * texel_width nx = (n + 1) * texel_width hn0 = height_data[0, n] hn1 = height_data[0, n + 1] hs0 = height_data[height_minus_one, n] hs1 = height_data[height_minus_one, n + 1] mesh.addFaceByPoints(x, 0, 0, nx, 0, 0, nx, hn1, 0) mesh.addFaceByPoints(nx, hn1, 0, x, hn0, 0, x, 0, 0) mesh.addFaceByPoints(x, 0, geo_height, nx, 0, geo_height, nx, hs1, geo_height) mesh.addFaceByPoints(nx, hs1, geo_height, x, hs0, geo_height, x, 0, geo_height) # west and east walls for n in range(0, height_minus_one): y = n * texel_height ny = (n + 1) * texel_height hw0 = height_data[n, 0] hw1 = height_data[n + 1, 0] he0 = height_data[n, width_minus_one] he1 = height_data[n + 1, width_minus_one] mesh.addFaceByPoints(0, 0, y, 0, 0, ny, 0, hw1, ny) mesh.addFaceByPoints(0, hw1, ny, 0, hw0, y, 0, 0, y) mesh.addFaceByPoints(geo_width, 0, y, geo_width, 0, ny, geo_width, he1, ny) mesh.addFaceByPoints(geo_width, he1, ny, geo_width, he0, y, geo_width, 0, y) mesh.calculateNormals(fast=True) scene_node.setMeshData(mesh.build()) return scene_node
class ImageDisplay(QWidget): def __init__(self, my_book, parent=None): super().__init__(parent) self.my_book = my_book # register metadata readers and writers md = my_book.get_meta_manager() md.register(C.MD_IZ,self._zoom_read,self._zoom_write) md.register(C.MD_IX,self._link_read,self._link_write) # Create our widgets including cursor_to_image and # image_to_cursor pushbuttons. self._uic() # set defaults in case no metadata self.cursor_to_image.setChecked(True) self.image_to_cursor.setChecked(False) self.zoom_factor = 0.25 self.png_path = None # disable all widgetry until we get some metadata self._disable() # end of __init__() # Disable our widgets because we have no image to show. def _disable(self): self.no_image = True self.last_index = None # compares unequal to any self.pix_map = QPixmap() self.image = QImage() self.cursor_to_image.setEnabled(False) self.image_to_cursor.setEnabled(False) self.zoom_pct.setEnabled(False) self.zoom_to_width.setEnabled(False) self.zoom_to_height.setEnabled(False) self.image_display.setPixmap(self.gray_image) self.image_display.setToolTip( _TR('Image view tooltip', 'Display of one scanned page (no images available)') ) # Enable our widgets, we have images to show. At this time the Book # has definitely created an edit view and a page model. def _enable(self): self.edit_view = self.my_book.get_edit_view() self.editor = self.edit_view.Editor # access to actual QTextEdit self.page_data = self.my_book.get_page_model() self.cursor_to_image.setEnabled(True) self.image_to_cursor.setEnabled(True) self.zoom_to_width.setEnabled(True) self.zoom_to_height.setEnabled(True) self.image_display.setToolTip( _TR('Image view tooltip', 'Display of one scanned page from the book') ) self.no_image = False self.zoom_pct.setEnabled(True) # the following triggers entry to _new_zoom_pct() below self.zoom_pct.setValue(int(100*self.zoom_factor)) # Metadata: read or write the {{IMAGEZOOM f}} section. # Parameter f should be a decimal number between 0.15 and 2.0 # but we do not depend on text the user could edit. def _zoom_read(self, qts, section, vers, parm): try: z = float(parm) # throws exception on a bad literal if math.isnan(z) or (z < 0.15) or (z > 2.0) : raise ValueError self.zoom_factor = z except: imageview_logger.error('Invalid IMAGEZOOM "{0}" ignored'.format(parm)) def _zoom_write(self, qts, section): qts << metadata.open_line(section, str(self.zoom_factor)) # Metadata: read or write the {{IMAGELINK b}} section. The parameter should # be an int 0/1/2/3. Bit 0 represents the state of cursor_to_image # (usually 1); bit 1 represents the state of image_to_cursor (usually 0). def _link_read(self, qts, section, vers, parm): try: b = int(parm) # exception on a bad literal if (b < 0) or (b > 3) : raise ValueError self.cursor_to_image.setChecked( True if b & 1 else False ) self.image_to_cursor.setChecked( True if b & 2 else False ) except : imageview_logger.error('Invalid IMAGELINKING "{0}" ignored'.format(parm)) def _link_write(self, qts, section): b = 0 if self.cursor_to_image.isChecked() : b |= 1 if self.image_to_cursor.isChecked() : b |= 2 qts << metadata.open_line(section, str(b)) # The Book calls here after it has loaded a book with defined page data, # passing the path to the folder containing the book. If we can find a # folder named 'pngs' in it we record that path and enable our widgets, # and fake a cursorMoved signal to display the current edit page. def set_path(self,book_folder_path): book_dir = QDir(book_folder_path) if book_dir.exists('pngs') : self.png_dir = QDir(book_dir.absoluteFilePath('pngs')) self._enable() self.cursor_move() # Come here to display or re-display an image. The last-displayed # page image index (if any) is in self.last_index. The desired page # index is passed as the argument, which may be: # * the same as last_index, for example on a change of zoom%. Just # redisplay the current page. # * negative or None if the cursor is "above" the first available page or on # a Page-Up keystroke. Display the gray image. # * greater than page_data.page_count() on a Page-Down keystroke, # display the last available page. # If different from last_index, try to load the .png file for that # page. If that fails, use the gray image. Otherwise display that # page and save it as last_index. def _show_page(self, page_index): if page_index != self.last_index : self.last_index = page_index # change of page, see if we have a filename for it self.pix_map = self.gray_image # assume failure... im_name = self.page_data.filename(page_index) if im_name : # pagedata has a filename; of course there is no guarantee # such a file exists now or ever did. im_name += '.png' if self.png_dir.exists(im_name) : self.image = QImage(self.png_dir.absoluteFilePath(im_name)) if not self.image.isNull(): # we loaded it ok, make a full-scale pixmap for display self.pix_map = QPixmap.fromImage(self.image,Qt.ColorOnly) # Whether new page or not, rescale to current zoom. The .resize method # takes a QSize; pix_map.size() returns one, and it supports * by a real. self.image_display.setPixmap(self.pix_map) self.image_display.resize( self.zoom_factor * self.pix_map.size() ) # Slot to receive the cursorMoved signal from the editview widget. If we # are in no_image state, do nothing. If the cursor_to_image switch is # not checked, do nothing. Else get the character position of # the high-end of the current edit selection, and use that to get the # current page index from pagedata, and pass that to _show_page. def cursor_move(self): if self.no_image : return if self.cursor_to_image.isChecked() : pos = self.editor.textCursor().selectionEnd() self._show_page( self.page_data.page_index(pos) ) # Slots to receive the signals from our zoom percent and zoom-to buttons. # The controls are disabled while we are in no_image state, so if a signal # arrives, we are not in that state. # # These are strictly internal hence _names. # Any change in the value of the zoom % spin-box including setValue(). def _new_zoom_pct(self,new_value): self.zoom_factor = self.zoom_pct.value() / 100 self._show_page(self.last_index) # Set a new zoom factor (a real) and update the zoom pct spinbox. # Setting zoom_pct triggers a signal to _new_zoom_pct above, and # thence to _show_page which repaints the page at the new scale value. def _set_zoom_real(self,new_value): zoom = max(new_value, ZOOM_FACTOR_MIN) zoom = min(zoom, ZOOM_FACTOR_MAX) self.zoom_factor = zoom self.zoom_pct.setValue(int(100*zoom)) # Re-implement keyPressEvent in order to provide zoom and page up/down. # ctrl-plus increases the image size by 1.25 # ctrl-minus decreases the image size by 0.8 # page-up displays the next-higher page # page-down displays the next-lower page def keyPressEvent(self, event): # assume we will not handle this key and clear its accepted flag event.ignore() if self.no_image or (self.last_index is None) : return # ignore keys until we are showing some image # We have images to show, check the key value. modkey = int( int(event.key() | (int(event.modifiers()) & C.KEYPAD_MOD_CLEAR)) ) if modkey in C.KEYS_ZOOM : event.accept() fac = (0.8) if (modkey == C.CTL_MINUS) else (1.25) self._set_zoom_real( fac * self.zoom_factor) elif (event.key() == Qt.Key_PageUp) or (event.key() == Qt.Key_PageDown) : event.accept() pgix = self.last_index + (1 if (event.key() == Qt.Key_PageDown) else -1) # If not paging off either end, show that page if pgix >= 0 and pgix < self.page_data.page_count() : self._show_page(pgix) if self.image_to_cursor.isChecked(): self.edit_view.show_position(self.page_data.position(pgix)) # Zoom to width and zoom to height are basically the same thing: # 1. Using the QImage of the current page in self.image, # scan its pixels to find the width (height) of the nonwhite area. # 2. Get the ratio of that to our image label's viewport width (height). # 3. Set that ratio as the zoom factor and redraw the image. # 5. Set the scroll position(s) of our scroll area to left-justify the text. # # We get access to the pixel data using QImage.bits() which gives us a # "sip.voidptr" object that we can index to get byte values. def _zoom_to_width(self): # Generic loop to scan inward from the left or right edge of one # column inward until a dark pixel is seen, returning that margin. def inner_loop(row_range, col_start, margin, col_step): pa, pb = 255, 255 # virtual white outside column for row in row_range: for col in range(col_start, margin, col_step): pc = color_table[ bytes_ptr[row+col] ] if (pa + pb + pc) < 24 : # black or dark gray trio margin = col # new, narrower, margin break # no need to look further on this row pa, pb = pb, pc # else shift 3-pixel window return margin - (2*col_step) # allow for 3-px window if self.no_image or self.image.isNull() : return # nothing to do scale_factor = 4 orig_rows = self.image.height() # number of pixels high orig_cols = self.image.width() # number of logical pixels across # Scale the image to 1/4 size (1/16 the pixel count) and then force # it to indexed-8 format, one byte per pixel. work_image = self.image.scaled( QSize(int(orig_cols/scale_factor),int(orig_rows/scale_factor)), Qt.KeepAspectRatio, Qt.FastTransformation) work_image = work_image.convertToFormat(QImage.Format_Indexed8,Qt.ColorOnly) # Get a reduced version of the color table by extracting just the GG # values of each entry, as a dict keyed by the pixel byte value. For # PNG-2, this gives [0,255] but it could have 8, 16, even 256 elements. color_table = { bytes([c]): int((work_image.color(c) >> 8) & 255) for c in range(work_image.colorCount()) } # Establish limits for the inner loop rows = work_image.height() # number of pixels high cols = work_image.width() # number of logical pixels across stride = (cols + 3) & (-4) # scan-line width in bytes bytes_ptr = work_image.bits() # uchar * a_bunch_o_pixels bytes_ptr.setsize(stride * rows) # make the pointer indexable # Scan in from left and from right to find the outermost dark spots. # Pages tend to start with many lines of white pixels so in hopes of # establishing a narrow margin quickly, scan from the middle to the # end, then do the top half. left_margin = inner_loop( range(int(rows/2)*stride, (rows-1)*stride, stride*2), 0, int(cols/2), 1 ) left_margin = inner_loop( range(0, int(rows/2)*stride, stride*2), 0, left_margin, 1 ) # Now do exactly the same but for the right margin. right_margin = inner_loop( range(int(rows/2)*stride, (rows-1)*stride, stride*2), cols-1, int(cols/2), -1 ) right_margin = inner_loop( range(0, int(rows/2)*stride, stride*2), cols-1, right_margin, -1 ) # Adjust the margins by the scale factor to fit the full size image. #left_margin = max(0,left_margin*scale_factor-scale_factor) #right_margin = min(orig_cols,right_margin*scale_factor+scale_factor) left_margin = left_margin*scale_factor right_margin = right_margin*scale_factor text_size = right_margin - left_margin + 2 port_width = self.scroll_area.viewport().width() # Set the new zoom factor, after limiting by min/max values self._set_zoom_real(port_width/text_size) # Set the scrollbar to show the page from its left margin. self.scroll_area.horizontalScrollBar().setValue( int( left_margin * self.zoom_factor) ) # and that completes zoom-to-width def _zoom_to_height(self): def dark_row(row_start, cols): ''' Scan one row of pixels and return True if it contains at least one 3-pixel blob of darkness, or False if not. ''' pa, pb = 255, 255 for c in range(row_start,row_start+cols): pc = color_table[ bytes_ptr[c] ] if (pa + pb + pc) < 24 : # black or dark gray trio return True pa, pb = pb, pc return False # row was all-white-ish if self.no_image or self.image.isNull() : return # nothing to do scale_factor = 4 orig_rows = self.image.height() # number of pixels high orig_cols = self.image.width() # number of logical pixels across # Scale the image to 1/4 size (1/16 the pixel count) and then force # it to indexed-8 format, one byte per pixel. work_image = self.image.scaled( QSize(int(orig_cols/scale_factor),int(orig_rows/scale_factor)), Qt.KeepAspectRatio, Qt.FastTransformation) work_image = work_image.convertToFormat(QImage.Format_Indexed8,Qt.ColorOnly) # Get a reduced version of the color table by extracting just the GG # values of each entry, as a dict keyed by the pixel byte value. For # PNG-2, this gives [0,255] but it could have 8, 16, even 256 elements. color_table = { bytes([c]): int((work_image.color(c) >> 8) & 255) for c in range(work_image.colorCount()) } rows = work_image.height() # number of pixels high cols = work_image.width() # number of logical pixels across stride = (cols + 3) & (-4) # scan-line width in bytes bytes_ptr = work_image.bits() # uchar * a_bunch_o_pixels bytes_ptr.setsize(stride * rows) # make the pointer indexable # Scan the image rows from the top down looking for one with darkness for top_row in range(rows): if dark_row(top_row*stride, cols): break if top_row > (rows/2) : # too much white, skip it return for bottom_row in range(rows-1, top_row, -1): if dark_row(bottom_row*stride, cols) : break # bottom_row has to be >= top_row. if they are too close together # set_zoom_real will limit the zoom to 200%. top_row = top_row*scale_factor bottom_row = bottom_row*scale_factor text_height = bottom_row - top_row + 1 port_height = self.scroll_area.viewport().height() self._set_zoom_real(port_height/text_height) self.scroll_area.verticalScrollBar().setValue( int( top_row * self.zoom_factor ) ) # and that completes zoom-to-height # Build the widgetary contents. The widget consists mostly of a vertical # layout with two items: A scrollArea containing a QLabel used to display # an image, and a horizontal layout containing the zoom controls. # TODO: figure out design and location of two cursor-link tool buttons. def _uic(self): # Function to return the actual width of the label text # of a widget. Get the fontMetrics and ask it for the width. def _label_width(widget): fm = widget.fontMetrics() return fm.width(widget.text()) # Create a gray field to use when no image is available self.gray_image = QPixmap(700,900) self.gray_image.fill(QColor("gray")) # Build the QLabel that displays the image pixmap. It gets all # available space and scales its contents to fit that space. self.image_display = QLabel() self.image_display.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.image_display.setScaledContents(True) # Create a scroll area within which to display the image. It will # create a horizontal and/or vertical scroll bar when # the image_display size exceeds the size of the scroll area. self.scroll_area = QScrollArea() self.scroll_area.setBackgroundRole(QPalette.Dark) self.scroll_area.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents) self.scroll_area.setWidget(self.image_display) # Make sure the scroll area does not swallow user keystrokes self.setFocusPolicy(Qt.ClickFocus) # focus into whole widget self.scroll_area.setFocusProxy(self) # you, pass it on. # Create the image-linking toolbuttons. # Cursor-to-image uses left-hands. c2i_on = QPixmap(':/hand-left-closed.png') c2i_off = QPixmap(':/hand-left-open.png') c2i_con = QIcon() c2i_con.addPixmap(c2i_on,QIcon.Normal,QIcon.On) c2i_con.addPixmap(c2i_off,QIcon.Normal,QIcon.Off) self.cursor_to_image = QToolButton() self.cursor_to_image.setCheckable(True) self.cursor_to_image.setContentsMargins(0,0,0,0) self.cursor_to_image.setIconSize(QSize(30,24)) self.cursor_to_image.setMaximumSize(QSize(32,26)) self.cursor_to_image.setIcon(c2i_con) # Image-to-cursor uses right-hands. i2c_on = QPixmap(':/hand-right-closed.png') i2c_off = QPixmap(':/hand-right-open.png') i2c_con = QIcon() i2c_con.addPixmap(i2c_on,QIcon.Normal,QIcon.On) i2c_con.addPixmap(i2c_off,QIcon.Normal,QIcon.Off) self.image_to_cursor = QToolButton() self.image_to_cursor.setCheckable(True) self.image_to_cursor.setContentsMargins(0,0,0,0) self.image_to_cursor.setIconSize(QSize(30,24)) self.image_to_cursor.setMaximumSize(QSize(32,26)) self.image_to_cursor.setIcon(i2c_con) # Create a spinbox to set the zoom from 15 to 200 and connect its # signal to our slot. self.zoom_pct = QSpinBox() self.zoom_pct.setRange( int(100*ZOOM_FACTOR_MIN),int(100*ZOOM_FACTOR_MAX)) self.zoom_pct.setToolTip( _TR('Imageview zoom control tooltip', 'Set the magnification of the page image') ) # Connect the valueChanged(int) signal as opposed to the # valueChanged(str) signal. self.zoom_pct.valueChanged['int'].connect(self._new_zoom_pct) # Create a label for the zoom spinbox. (the label is not saved as a # class member, its layout will keep it in focus) Not translating # the word "Zoom". pct_label = QLabel( '&Zoom {0}-{1}%'.format( str(self.zoom_pct.minimum() ), str(self.zoom_pct.maximum() ) ) ) pct_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) pct_label.setBuddy(self.zoom_pct) # Create the to-width and to-height zoom buttons. Make # sure their widths are equal after translation. self.zoom_to_width = QPushButton( _TR('Imageview zoom control button name','to Width') ) self.zoom_to_width.setToolTip( _TR('Imageview zoom control tooltip', 'Adjust the image to fill the window side to side.') ) self.zoom_to_width.clicked.connect(self._zoom_to_width) self.zoom_to_height = QPushButton( _TR('Imageview zoom control button name','to Height') ) self.zoom_to_height.setToolTip( _TR('Imageview zoom control tooltip', 'Adjust the image to fill the window top to bottom.') ) self.zoom_to_height.clicked.connect(self._zoom_to_height) w = 20 + max(_label_width(self.zoom_to_height),_label_width(self.zoom_to_width)) self.zoom_to_height.setMinimumWidth(w) self.zoom_to_width.setMinimumWidth(w) # Create an HBox for the top of the panel which contains only # the cursor-to-image link button. tophbox = QHBoxLayout() tophbox.setContentsMargins(0,0,0,0) tophbox.addWidget(self.cursor_to_image,0) tophbox.addStretch() # left-align the button # Create an HBox layout to contain the above controls, using # spacers left and right to center them and a spacers between # to control the spacing. zhbox = QHBoxLayout() zhbox.setContentsMargins(0,0,0,0) zhbox.addWidget(self.image_to_cursor,0) zhbox.addStretch(2) # left and right spacers have stretch 2 zhbox.addWidget(pct_label,0) zhbox.addWidget(self.zoom_pct,0) zhbox.addStretch(1) # spacers between widgets are stretch 1 zhbox.addWidget(self.zoom_to_height,0) zhbox.addSpacing(10) # juuuust a little space between buttons zhbox.addWidget(self.zoom_to_width,0) zhbox.addStretch(2) # right side spacer # With all the pieces in hand, create our layout with a stack of # image over row of controls. vbox = QVBoxLayout() vbox.setContentsMargins(0,0,0,0) vbox.addLayout(tophbox,0) # The image gets a high stretch and default alignment. vbox.addWidget(self.scroll_area,2) vbox.addLayout(zhbox,0) self.setLayout(vbox) # And that completes the UI setup.
class Viewer(QMainWindow): # Brightness. Gloom, Quarter, Half, ThreeQuarters, Full = range(5) # Brightness value map. brightnessValueMap = { Gloom: 0, Quarter: 64, Half: 128, ThreeQuarters: 191, Full: 255, } def __init__(self): """ Constructor initializes a default value for the brightness, creates the main menu entries, and constructs a central widget that contains enough space for images to be displayed. """ super(Viewer, self).__init__() self.scaledImage = QImage() self.menuMap = {} self.path = '' self.brightness = 255 self.setWindowTitle("QImage Color Separations") self.createMenus() self.setCentralWidget(self.createCentralWidget()) def createMenus(self): """ Creates a main menu with two entries: a File menu, to allow the image to be selected, and a Brightness menu to allow the brightness of the separations to be changed. Initially, the Brightness menu items are disabled, but the first entry in the menu is checked to reflect the default brightness. """ self.fileMenu = QMenu("&File", self) self.brightnessMenu = QMenu("&Brightness", self) self.openAction = self.fileMenu.addAction("&Open...") self.openAction.setShortcut(QKeySequence('Ctrl+O')) self.saveAction = self.fileMenu.addAction("&Save...") self.saveAction.setShortcut(QKeySequence('Ctrl+S')) self.saveAction.setEnabled(False) self.quitAction = self.fileMenu.addAction("E&xit") self.quitAction.setShortcut(QKeySequence('Ctrl+Q')) self.noBrightness = self.brightnessMenu.addAction("&0%") self.noBrightness.setCheckable(True) self.quarterBrightness = self.brightnessMenu.addAction("&25%") self.quarterBrightness.setCheckable(True) self.halfBrightness = self.brightnessMenu.addAction("&50%") self.halfBrightness.setCheckable(True) self.threeQuartersBrightness = self.brightnessMenu.addAction("&75%") self.threeQuartersBrightness.setCheckable(True) self.fullBrightness = self.brightnessMenu.addAction("&100%") self.fullBrightness.setCheckable(True) self.menuMap[self.noBrightness] = self.Gloom self.menuMap[self.quarterBrightness] = self.Quarter self.menuMap[self.halfBrightness] = self.Half self.menuMap[self.threeQuartersBrightness] = self.ThreeQuarters self.menuMap[self.fullBrightness] = self.Full self.currentBrightness = self.fullBrightness self.currentBrightness.setChecked(True) self.brightnessMenu.setEnabled(False) self.menuBar().addMenu(self.fileMenu) self.menuBar().addMenu(self.brightnessMenu) self.openAction.triggered.connect(self.chooseFile) self.saveAction.triggered.connect(self.saveImage) self.quitAction.triggered.connect(QApplication.instance().quit) self.brightnessMenu.triggered.connect(self.setBrightness) def createCentralWidget(self): """ Constructs a central widget for the window consisting of a two-by-two grid of labels, each of which will contain an image. We restrict the size of the labels to 256 pixels, and ensure that the window cannot be resized. """ frame = QFrame(self) grid = QGridLayout(frame) grid.setSpacing(8) grid.setContentsMargins(4, 4, 4, 4) self.layout().setSizeConstraint(QLayout.SetFixedSize) labelSize = QSize(256, 256) self.finalWidget = FinalWidget(frame, "Final image", labelSize) self.cyanWidget = ScreenWidget(frame, Qt.cyan, "Cyan", ScreenWidget.Cyan, labelSize) self.magentaWidget = ScreenWidget(frame, Qt.magenta, "Magenta", ScreenWidget.Magenta, labelSize) self.yellowWidget = ScreenWidget(frame, Qt.yellow, "Yellow", ScreenWidget.Yellow, labelSize) self.cyanWidget.imageChanged.connect(self.createImage) self.magentaWidget.imageChanged.connect(self.createImage) self.yellowWidget.imageChanged.connect(self.createImage) grid.addWidget(self.finalWidget, 0, 0, Qt.AlignTop | Qt.AlignHCenter) grid.addWidget(self.cyanWidget, 0, 1, Qt.AlignTop | Qt.AlignHCenter) grid.addWidget(self.magentaWidget, 1, 0, Qt.AlignTop | Qt.AlignHCenter) grid.addWidget(self.yellowWidget, 1, 1, Qt.AlignTop | Qt.AlignHCenter) return frame def chooseFile(self): """ Provides a dialog window to allow the user to specify an image file. If a file is selected, the appropriate function is called to process and display it. """ imageFile, _ = QFileDialog.getOpenFileName(self, "Choose an image file to open", self.path, "Images (*.*)") if imageFile != '': self.openImageFile(imageFile) self.path = imageFile def setBrightness(self, action): """ Changes the value of the brightness according to the entry selected in the Brightness menu. The selected entry is checked, and the previously selected entry is unchecked. The color separations are updated to use the new value for the brightness. """ if action not in self.menuMap or self.scaledImage.isNull(): return self.brightness = self.brightnessValueMap.get(self.menuMap[action]) if self.brightness is None: return self.currentBrightness.setChecked(False) self.currentBrightness = action self.currentBrightness.setChecked(True) self.createImage() def openImageFile(self, imageFile): """ Load the image from the file given, and create four pixmaps based on the original image. The window caption is set, and the Brightness menu enabled if the image file can be loaded. """ originalImage = QImage() if originalImage.load(imageFile): self.setWindowTitle(imageFile) self.saveAction.setEnabled(True) self.brightnessMenu.setEnabled(True) self.scaledImage = originalImage.scaled(256, 256, Qt.KeepAspectRatio) self.cyanWidget.setImage(self.scaledImage) self.magentaWidget.setImage(self.scaledImage) self.yellowWidget.setImage(self.scaledImage) self.createImage() else: QMessageBox.warning(self, "Cannot open file", "The selected file could not be opened.", QMessageBox.Cancel, QMessageBox.NoButton, QMessageBox.NoButton) def createImage(self): """ Creates an image by combining the contents of the three screens to present a page preview. The image associated with each screen is separated into cyan, magenta, and yellow components. We add up the values for each component from the three screen images, and subtract the totals from the maximum value for each corresponding primary color. """ newImage = self.scaledImage.copy() image1 = self.cyanWidget.image() image2 = self.magentaWidget.image() image3 = self.yellowWidget.image() darkness = 255 - self.brightness for y in range(newImage.height()): for x in range(newImage.width()): # Create three screens, using the quantities of the source CMY # components to determine how much of each of the inks are to # be put on each screen. p1 = image1.pixel(x, y) cyan1 = float(255 - qRed(p1)) magenta1 = float(255 - qGreen(p1)) yellow1 = float(255 - qBlue(p1)) p2 = image2.pixel(x, y) cyan2 = float(255 - qRed(p2)) magenta2 = float(255 - qGreen(p2)) yellow2 = float(255 - qBlue(p2)) p3 = image3.pixel(x, y) cyan3 = float(255 - qRed(p3)) magenta3 = float(255 - qGreen(p3)) yellow3 = float(255 - qBlue(p3)) newColor = QColor( max(255 - int(cyan1 + cyan2 + cyan3) - darkness, 0), max(255 - int(magenta1 + magenta2 + magenta3) - darkness, 0), max(255 - int(yellow1 + yellow2 + yellow3) - darkness, 0)) newImage.setPixel(x, y, newColor.rgb()) self.finalWidget.setPixmap(QPixmap.fromImage(newImage)) def saveImage(self): """ Provides a dialog window to allow the user to save the image file. """ imageFile, _ = QFileDialog.getSaveFileName(self, "Choose a filename to save the image", "", "Images (*.png)") info = QFileInfo(imageFile) if info.baseName() != '': newImageFile = QFileInfo(info.absoluteDir(), info.baseName() + '.png').absoluteFilePath() if not self.finalWidget.pixmap().save(newImageFile, 'PNG'): QMessageBox.warning(self, "Cannot save file", "The file could not be saved.", QMessageBox.Cancel, QMessageBox.NoButton, QMessageBox.NoButton) else: QMessageBox.warning(self, "Cannot save file", "Please enter a valid filename.", QMessageBox.Cancel, QMessageBox.NoButton, QMessageBox.NoButton)