diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 706e3b4ab6..015df60143 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -123,6 +123,7 @@ defs['cover_grid_texture'] = None defs['show_vl_tabs'] = False defs['show_highlight_toggle_button'] = False defs['add_comments_to_email'] = False +defs['cb_preserve_aspect_ratio'] = False del defs # }}} diff --git a/src/calibre/gui2/cover_flow.py b/src/calibre/gui2/cover_flow.py index 81cdf9b90d..1eb11adac2 100644 --- a/src/calibre/gui2/cover_flow.py +++ b/src/calibre/gui2/cover_flow.py @@ -21,6 +21,7 @@ pictureflow, pictureflowerror = plugins['pictureflow'] if pictureflow is not None: class EmptyImageList(pictureflow.FlowImages): + def __init__(self): pictureflow.FlowImages.__init__(self) @@ -108,7 +109,6 @@ if pictureflow is not None: def image(self, index): return self.model.cover(index) - class CoverFlow(pictureflow.PictureFlow): dc_signal = pyqtSignal() @@ -125,6 +125,10 @@ if pictureflow is not None: type=Qt.QueuedConnection) self.context_menu = None self.setContextMenuPolicy(Qt.DefaultContextMenu) + try: + self.setPreserveAspectRatio(gprefs['cb_preserve_aspect_ratio']) + except AttributeError: + pass # source checkout without updated binary if hasattr(self, 'setSubtitleFont'): self.setSubtitleFont(QFont(rating_font())) if not gprefs['cover_browser_reflections']: @@ -290,7 +294,6 @@ class CoverFlowMixin(object): self.library_view.setCurrentIndex(idx) self.library_view.scroll_to_row(idx.row()) - def show_cover_browser(self): d = CBDialog(self, self.cover_flow) d.addAction(self.cb_splitter.action_toggle) @@ -313,7 +316,6 @@ class CoverFlowMixin(object): self.cb_dialog = None self.cb_splitter.button.set_state_to_show() - def sync_cf_to_listview(self, current, previous): if self.cover_flow_sync_flag and self.cover_flow.isVisible() and \ self.cover_flow.currentSlide() != current.row(): diff --git a/src/calibre/gui2/pictureflow/pictureflow.cpp b/src/calibre/gui2/pictureflow/pictureflow.cpp index 173a080301..d9811f02e7 100644 --- a/src/calibre/gui2/pictureflow/pictureflow.cpp +++ b/src/calibre/gui2/pictureflow/pictureflow.cpp @@ -318,6 +318,9 @@ struct SlideInfo PFreal cy; }; +static const QString OFFSET_KEY("offset"); +static const QString WIDTH_KEY("width"); + // PicturePlowPrivate {{{ class PictureFlowPrivate @@ -367,6 +370,7 @@ public: QTime previousPosTimestamp; int pixelDistanceMoved; int pixelsToMovePerSlide; + bool preserveAspectRatio; QFont subtitleFont; void setImages(FlowImages *images); @@ -421,6 +425,7 @@ PictureFlowPrivate::PictureFlowPrivate(PictureFlow* w, int queueLength_) slideHeight = 200; fontSize = 10; doReflections = true; + preserveAspectRatio = false; centerIndex = 0; queueLength = queueLength_; @@ -598,41 +603,58 @@ void PictureFlowPrivate::resetSlides() } } -static QImage prepareSurface(QImage img, int w, int h, bool doReflections) +static QImage prepareSurface(QImage srcimg, int w, int h, bool doReflections, bool preserveAspectRatio) { - img = img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + // slightly larger, to accommodate for the reflection + int hs = int(h * REFLECTION_FACTOR), left = 0, top = 0, a = 0, r = 0, g = 0, b = 0, ht, x, y, bpp; + QImage img = (preserveAspectRatio) ? QImage(w, h, srcimg.format()) : srcimg.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + QRgb color; - // slightly larger, to accommodate for the reflection - int hs = int(h * REFLECTION_FACTOR); + // offscreen buffer: black is sweet + QImage result(hs, w, QImage::Format_RGB16); + result.fill(0); - // offscreen buffer: black is sweet - QImage result(hs, w, QImage::Format_RGB16); - result.fill(0); - - // transpose the image, this is to speed-up the rendering - // because we process one column at a time - // (and much better and faster to work row-wise, i.e in one scanline) - for(int x = 0; x < w; x++) - for(int y = 0; y < h; y++) - result.setPixel(y, x, img.pixel(x, y)); - - if (doReflections) { - // create the reflection - int ht = hs - h; - for(int x = 0; x < w; x++) - for(int y = 0; y < ht; y++) - { - QRgb color = img.pixel(x, img.height()-y-1); - //QRgb565 color = img.scanLine(img.height()-y-1) + x*sizeof(QRgb565); //img.pixel(x, img.height()-y-1); - int a = qAlpha(color); - int r = qRed(color) * a / 256 * (ht - y) / ht * 3/5; - int g = qGreen(color) * a / 256 * (ht - y) / ht * 3/5; - int b = qBlue(color) * a / 256 * (ht - y) / ht * 3/5; - result.setPixel(h+y, x, qRgb(r, g, b)); + if (preserveAspectRatio) { + QImage temp = srcimg.scaled(w, h, Qt::KeepAspectRatio, Qt::SmoothTransformation); + img = QImage(w, h, temp.format()); + img.fill(0); + left = (w - temp.width()) / 2; + top = h - temp.height(); + bpp = img.bytesPerLine() / img.width(); + x = temp.width() * bpp; + result.setText(OFFSET_KEY, QString::number(left)); + result.setText(WIDTH_KEY, QString::number(temp.width())); + for (y = 0; y < temp.height(); y++) { + const uchar *src = temp.scanLine(y); + uchar *dest = img.scanLine(top + y) + (bpp * left); + memcpy(dest, src, x); } - } + } - return result; + // transpose the image, this is to speed-up the rendering + // because we process one column at a time + // (and much better and faster to work row-wise, i.e in one scanline) + for(x = 0; x < w; x++) + for(y = 0; y < h; y++) + result.setPixel(y, x, img.pixel(x, y)); + + if (doReflections) { + // create the reflection + ht = hs - h; + for(x = 0; x < w; x++) + for(y = 0; y < ht; y++) + { + color = img.pixel(x, img.height()-y-1); + //QRgb565 color = img.scanLine(img.height()-y-1) + x*sizeof(QRgb565); //img.pixel(x, img.height()-y-1); + a = qAlpha(color); + r = qRed(color) * a / 256 * (ht - y) / ht * 3/5; + g = qGreen(color) * a / 256 * (ht - y) / ht * 3/5; + b = qBlue(color) * a / 256 * (ht - y) / ht * 3/5; + result.setPixel(h+y, x, qRgb(r, g, b)); + } + } + + return result; } @@ -668,12 +690,12 @@ QImage* PictureFlowPrivate::surface(int slideIndex) painter.setBrush(QBrush()); painter.drawRect(2, 2, slideWidth-3, slideHeight-3); painter.end(); - blankSurface = prepareSurface(blankSurface, slideWidth, slideHeight, doReflections); + blankSurface = prepareSurface(blankSurface, slideWidth, slideHeight, doReflections, preserveAspectRatio); } return &blankSurface; } - surfaceCache.insert(slideIndex, new QImage(prepareSurface(img, slideWidth, slideHeight, doReflections))); + surfaceCache.insert(slideIndex, new QImage(prepareSurface(img, slideWidth, slideHeight, doReflections, preserveAspectRatio))); return surfaceCache[slideIndex]; } @@ -874,8 +896,7 @@ QRect PictureFlowPrivate::renderCenterSlide(const SlideInfo &slide) { // Renders a slide to offscreen buffer. Returns a rect of the rendered area. // alpha=256 means normal, alpha=0 is fully black, alpha=128 half transparent // col1 and col2 limit the column for rendering. -QRect PictureFlowPrivate::renderSlide(const SlideInfo &slide, int alpha, -int col1, int col2) +QRect PictureFlowPrivate::renderSlide(const SlideInfo &slide, int alpha, int col1, int col2) { QImage* src = surface(slide.slideIndex); if(!src) @@ -913,6 +934,13 @@ int col1, int col2) bool flag = false; rect.setLeft(xi); + int img_offset = 0, img_width = 0; + bool slide_moving_to_center = false; + if (preserveAspectRatio) { + img_offset = src->text(OFFSET_KEY).toInt(); + img_width = src->text(WIDTH_KEY).toInt(); + slide_moving_to_center = slide.slideIndex == target && target != centerIndex; + } for(int x = qMax(xi, col1); x <= col2; x++) { PFreal hity = 0; @@ -935,6 +963,17 @@ int col1, int col2) break; if(column < 0) continue; + if (preserveAspectRatio && !slide_moving_to_center) { + // We dont want a black border at the edge of narrow images when the images are in the left or right stacks + if (slide.slideIndex < centerIndex) { + column = qMin(column + img_offset, sw - 1); + } else if (slide.slideIndex == centerIndex) { + if (target > centerIndex) column = qMin(column + img_offset, sw - 1); + else if (target < centerIndex) column = qMax(column - sw + img_offset + img_width, 0); + } else { + column = qMax(column - sw + img_offset + img_width, 0); + } + } rect.setRight(x); if(!flag) @@ -1196,6 +1235,17 @@ void PictureFlow::setSlideSize(QSize size) d->setSlideSize(size); } +bool PictureFlow::preserveAspectRatio() const +{ + return d->preserveAspectRatio; +} + +void PictureFlow::setPreserveAspectRatio(bool preserve) +{ + d->preserveAspectRatio = preserve; + clearCaches(); +} + void PictureFlow::setSubtitleFont(QFont font) { d->subtitleFont = font; diff --git a/src/calibre/gui2/pictureflow/pictureflow.h b/src/calibre/gui2/pictureflow/pictureflow.h index bc427e8580..9d50b89edc 100644 --- a/src/calibre/gui2/pictureflow/pictureflow.h +++ b/src/calibre/gui2/pictureflow/pictureflow.h @@ -93,6 +93,7 @@ Q_OBJECT Q_PROPERTY(int currentSlide READ currentSlide WRITE setCurrentSlide) Q_PROPERTY(QSize slideSize READ slideSize WRITE setSlideSize) Q_PROPERTY(QFont subtitleFont READ subtitleFont WRITE setSubtitleFont) + Q_PROPERTY(bool preserveAspectRatio READ preserveAspectRatio WRITE setPreserveAspectRatio) public: /*! @@ -121,6 +122,16 @@ public: */ void setSlideSize(QSize size); + /*! + Returns whether aspect ration is preserved when scaling images + */ + bool preserveAspectRatio() const; + + /*! + Whether to preserve aspect ration when scaling images + */ + void setPreserveAspectRatio(bool preserve); + /*! Turn the reflections on/off. */ diff --git a/src/calibre/gui2/pictureflow/pictureflow.sip b/src/calibre/gui2/pictureflow/pictureflow.sip index 0fab379147..3754a538ce 100644 --- a/src/calibre/gui2/pictureflow/pictureflow.sip +++ b/src/calibre/gui2/pictureflow/pictureflow.sip @@ -41,6 +41,10 @@ public : void setSlideSize(QSize size); + bool preserveAspectRatio() const; + + void setPreserveAspectRatio(bool preserve); + QFont subtitleFont() const; void setSubtitleFont(QFont font); diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index e886cf8dec..6ced51f3e1 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -183,6 +183,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): r('use_roman_numerals_for_series_number', config) r('separate_cover_flow', config, restart_required=True) r('cb_fullscreen', gprefs) + r('cb_preserve_aspect_ratio', gprefs) choices = [(_('Off'), 'off'), (_('Small'), 'small'), (_('Medium'), 'medium'), (_('Large'), 'large')] @@ -461,6 +462,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): gui.library_view.refresh_book_details() if hasattr(gui.cover_flow, 'setShowReflections'): gui.cover_flow.setShowReflections(gprefs['cover_browser_reflections']) + gui.cover_flow.setPreserveAspectRatio(gprefs['cb_preserve_aspect_ratio']) gui.library_view.refresh_row_sizing() gui.grid_view.refresh_settings() diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index c1153d49b3..6721fb1e7c 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -897,7 +897,7 @@ a few top-level elements. - + Qt::Vertical @@ -913,14 +913,14 @@ a few top-level elements. - + When showing cover browser in separate window, show it &fullscreen - + margin-left: 1.5em @@ -940,6 +940,17 @@ a few top-level elements. + + + + Show covers in their original aspect ratio instead of resizing +them to all have the same width and height + + + Preserve &aspect ratio of covers displayed in the cover browser + + +