Cover Browser: Add an option to show covers with their original aspect ratio instead of resizing them all to have the same width and height. Option is in Preferences->Look & Feel->Cover Browser. Fixes #1295902 [[Enhancement Request] Allow cover browser to show book covers in actual dimensions/orientation](https://bugs.launchpad.net/calibre/+bug/1295902)

This commit is contained in:
Kovid Goyal 2014-03-22 17:06:02 +05:30
parent cfad8a5076
commit eaae5b235e
7 changed files with 121 additions and 40 deletions

View File

@ -123,6 +123,7 @@ defs['cover_grid_texture'] = None
defs['show_vl_tabs'] = False defs['show_vl_tabs'] = False
defs['show_highlight_toggle_button'] = False defs['show_highlight_toggle_button'] = False
defs['add_comments_to_email'] = False defs['add_comments_to_email'] = False
defs['cb_preserve_aspect_ratio'] = False
del defs del defs
# }}} # }}}

View File

@ -21,6 +21,7 @@ pictureflow, pictureflowerror = plugins['pictureflow']
if pictureflow is not None: if pictureflow is not None:
class EmptyImageList(pictureflow.FlowImages): class EmptyImageList(pictureflow.FlowImages):
def __init__(self): def __init__(self):
pictureflow.FlowImages.__init__(self) pictureflow.FlowImages.__init__(self)
@ -108,7 +109,6 @@ if pictureflow is not None:
def image(self, index): def image(self, index):
return self.model.cover(index) return self.model.cover(index)
class CoverFlow(pictureflow.PictureFlow): class CoverFlow(pictureflow.PictureFlow):
dc_signal = pyqtSignal() dc_signal = pyqtSignal()
@ -125,6 +125,10 @@ if pictureflow is not None:
type=Qt.QueuedConnection) type=Qt.QueuedConnection)
self.context_menu = None self.context_menu = None
self.setContextMenuPolicy(Qt.DefaultContextMenu) self.setContextMenuPolicy(Qt.DefaultContextMenu)
try:
self.setPreserveAspectRatio(gprefs['cb_preserve_aspect_ratio'])
except AttributeError:
pass # source checkout without updated binary
if hasattr(self, 'setSubtitleFont'): if hasattr(self, 'setSubtitleFont'):
self.setSubtitleFont(QFont(rating_font())) self.setSubtitleFont(QFont(rating_font()))
if not gprefs['cover_browser_reflections']: if not gprefs['cover_browser_reflections']:
@ -290,7 +294,6 @@ class CoverFlowMixin(object):
self.library_view.setCurrentIndex(idx) self.library_view.setCurrentIndex(idx)
self.library_view.scroll_to_row(idx.row()) self.library_view.scroll_to_row(idx.row())
def show_cover_browser(self): def show_cover_browser(self):
d = CBDialog(self, self.cover_flow) d = CBDialog(self, self.cover_flow)
d.addAction(self.cb_splitter.action_toggle) d.addAction(self.cb_splitter.action_toggle)
@ -313,7 +316,6 @@ class CoverFlowMixin(object):
self.cb_dialog = None self.cb_dialog = None
self.cb_splitter.button.set_state_to_show() self.cb_splitter.button.set_state_to_show()
def sync_cf_to_listview(self, current, previous): def sync_cf_to_listview(self, current, previous):
if self.cover_flow_sync_flag and self.cover_flow.isVisible() and \ if self.cover_flow_sync_flag and self.cover_flow.isVisible() and \
self.cover_flow.currentSlide() != current.row(): self.cover_flow.currentSlide() != current.row():

View File

@ -318,6 +318,9 @@ struct SlideInfo
PFreal cy; PFreal cy;
}; };
static const QString OFFSET_KEY("offset");
static const QString WIDTH_KEY("width");
// PicturePlowPrivate {{{ // PicturePlowPrivate {{{
class PictureFlowPrivate class PictureFlowPrivate
@ -367,6 +370,7 @@ public:
QTime previousPosTimestamp; QTime previousPosTimestamp;
int pixelDistanceMoved; int pixelDistanceMoved;
int pixelsToMovePerSlide; int pixelsToMovePerSlide;
bool preserveAspectRatio;
QFont subtitleFont; QFont subtitleFont;
void setImages(FlowImages *images); void setImages(FlowImages *images);
@ -421,6 +425,7 @@ PictureFlowPrivate::PictureFlowPrivate(PictureFlow* w, int queueLength_)
slideHeight = 200; slideHeight = 200;
fontSize = 10; fontSize = 10;
doReflections = true; doReflections = true;
preserveAspectRatio = false;
centerIndex = 0; centerIndex = 0;
queueLength = queueLength_; 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 // offscreen buffer: black is sweet
int hs = int(h * REFLECTION_FACTOR); QImage result(hs, w, QImage::Format_RGB16);
result.fill(0);
// offscreen buffer: black is sweet if (preserveAspectRatio) {
QImage result(hs, w, QImage::Format_RGB16); QImage temp = srcimg.scaled(w, h, Qt::KeepAspectRatio, Qt::SmoothTransformation);
result.fill(0); img = QImage(w, h, temp.format());
img.fill(0);
// transpose the image, this is to speed-up the rendering left = (w - temp.width()) / 2;
// because we process one column at a time top = h - temp.height();
// (and much better and faster to work row-wise, i.e in one scanline) bpp = img.bytesPerLine() / img.width();
for(int x = 0; x < w; x++) x = temp.width() * bpp;
for(int y = 0; y < h; y++) result.setText(OFFSET_KEY, QString::number(left));
result.setPixel(y, x, img.pixel(x, y)); result.setText(WIDTH_KEY, QString::number(temp.width()));
for (y = 0; y < temp.height(); y++) {
if (doReflections) { const uchar *src = temp.scanLine(y);
// create the reflection uchar *dest = img.scanLine(top + y) + (bpp * left);
int ht = hs - h; memcpy(dest, src, x);
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));
} }
} }
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.setBrush(QBrush());
painter.drawRect(2, 2, slideWidth-3, slideHeight-3); painter.drawRect(2, 2, slideWidth-3, slideHeight-3);
painter.end(); painter.end();
blankSurface = prepareSurface(blankSurface, slideWidth, slideHeight, doReflections); blankSurface = prepareSurface(blankSurface, slideWidth, slideHeight, doReflections, preserveAspectRatio);
} }
return &blankSurface; 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]; 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. // 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 // alpha=256 means normal, alpha=0 is fully black, alpha=128 half transparent
// col1 and col2 limit the column for rendering. // col1 and col2 limit the column for rendering.
QRect PictureFlowPrivate::renderSlide(const SlideInfo &slide, int alpha, QRect PictureFlowPrivate::renderSlide(const SlideInfo &slide, int alpha, int col1, int col2)
int col1, int col2)
{ {
QImage* src = surface(slide.slideIndex); QImage* src = surface(slide.slideIndex);
if(!src) if(!src)
@ -913,6 +934,13 @@ int col1, int col2)
bool flag = false; bool flag = false;
rect.setLeft(xi); 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++) for(int x = qMax(xi, col1); x <= col2; x++)
{ {
PFreal hity = 0; PFreal hity = 0;
@ -935,6 +963,17 @@ int col1, int col2)
break; break;
if(column < 0) if(column < 0)
continue; 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); rect.setRight(x);
if(!flag) if(!flag)
@ -1196,6 +1235,17 @@ void PictureFlow::setSlideSize(QSize size)
d->setSlideSize(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) void PictureFlow::setSubtitleFont(QFont font)
{ {
d->subtitleFont = font; d->subtitleFont = font;

View File

@ -93,6 +93,7 @@ Q_OBJECT
Q_PROPERTY(int currentSlide READ currentSlide WRITE setCurrentSlide) Q_PROPERTY(int currentSlide READ currentSlide WRITE setCurrentSlide)
Q_PROPERTY(QSize slideSize READ slideSize WRITE setSlideSize) Q_PROPERTY(QSize slideSize READ slideSize WRITE setSlideSize)
Q_PROPERTY(QFont subtitleFont READ subtitleFont WRITE setSubtitleFont) Q_PROPERTY(QFont subtitleFont READ subtitleFont WRITE setSubtitleFont)
Q_PROPERTY(bool preserveAspectRatio READ preserveAspectRatio WRITE setPreserveAspectRatio)
public: public:
/*! /*!
@ -121,6 +122,16 @@ public:
*/ */
void setSlideSize(QSize size); 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. Turn the reflections on/off.
*/ */

View File

@ -41,6 +41,10 @@ public :
void setSlideSize(QSize size); void setSlideSize(QSize size);
bool preserveAspectRatio() const;
void setPreserveAspectRatio(bool preserve);
QFont subtitleFont() const; QFont subtitleFont() const;
void setSubtitleFont(QFont font); void setSubtitleFont(QFont font);

View File

@ -183,6 +183,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('use_roman_numerals_for_series_number', config) r('use_roman_numerals_for_series_number', config)
r('separate_cover_flow', config, restart_required=True) r('separate_cover_flow', config, restart_required=True)
r('cb_fullscreen', gprefs) r('cb_fullscreen', gprefs)
r('cb_preserve_aspect_ratio', gprefs)
choices = [(_('Off'), 'off'), (_('Small'), 'small'), choices = [(_('Off'), 'off'), (_('Small'), 'small'),
(_('Medium'), 'medium'), (_('Large'), 'large')] (_('Medium'), 'medium'), (_('Large'), 'large')]
@ -461,6 +462,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
gui.library_view.refresh_book_details() gui.library_view.refresh_book_details()
if hasattr(gui.cover_flow, 'setShowReflections'): if hasattr(gui.cover_flow, 'setShowReflections'):
gui.cover_flow.setShowReflections(gprefs['cover_browser_reflections']) gui.cover_flow.setShowReflections(gprefs['cover_browser_reflections'])
gui.cover_flow.setPreserveAspectRatio(gprefs['cb_preserve_aspect_ratio'])
gui.library_view.refresh_row_sizing() gui.library_view.refresh_row_sizing()
gui.grid_view.refresh_settings() gui.grid_view.refresh_settings()

View File

@ -897,7 +897,7 @@ a few top-level elements.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0" colspan="2"> <item row="6" column="0" colspan="2">
<spacer name="verticalSpacer_4"> <spacer name="verticalSpacer_4">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
@ -913,14 +913,14 @@ a few top-level elements.</string>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QSpinBox" name="opt_cover_flow_queue_length"/> <widget class="QSpinBox" name="opt_cover_flow_queue_length"/>
</item> </item>
<item row="3" column="0" colspan="2"> <item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="opt_cb_fullscreen"> <widget class="QCheckBox" name="opt_cb_fullscreen">
<property name="text"> <property name="text">
<string>When showing cover browser in separate window, show it &amp;fullscreen</string> <string>When showing cover browser in separate window, show it &amp;fullscreen</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0" colspan="2"> <item row="5" column="0" colspan="2">
<widget class="QLabel" name="fs_help_msg"> <widget class="QLabel" name="fs_help_msg">
<property name="styleSheet"> <property name="styleSheet">
<string notr="true">margin-left: 1.5em</string> <string notr="true">margin-left: 1.5em</string>
@ -940,6 +940,17 @@ a few top-level elements.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="opt_cb_preserve_aspect_ratio">
<property name="toolTip">
<string>Show covers in their original aspect ratio instead of resizing
them to all have the same width and height</string>
</property>
<property name="text">
<string>Preserve &amp;aspect ratio of covers displayed in the cover browser</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</widget> </widget>