mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
parent
cfad8a5076
commit
eaae5b235e
@ -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
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
@ -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():
|
||||||
|
@ -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,36 +603,53 @@ 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
|
// slightly larger, to accommodate for the reflection
|
||||||
int hs = int(h * REFLECTION_FACTOR);
|
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;
|
||||||
|
|
||||||
// offscreen buffer: black is sweet
|
// offscreen buffer: black is sweet
|
||||||
QImage result(hs, w, QImage::Format_RGB16);
|
QImage result(hs, w, QImage::Format_RGB16);
|
||||||
result.fill(0);
|
result.fill(0);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// transpose the image, this is to speed-up the rendering
|
// transpose the image, this is to speed-up the rendering
|
||||||
// because we process one column at a time
|
// because we process one column at a time
|
||||||
// (and much better and faster to work row-wise, i.e in one scanline)
|
// (and much better and faster to work row-wise, i.e in one scanline)
|
||||||
for(int x = 0; x < w; x++)
|
for(x = 0; x < w; x++)
|
||||||
for(int y = 0; y < h; y++)
|
for(y = 0; y < h; y++)
|
||||||
result.setPixel(y, x, img.pixel(x, y));
|
result.setPixel(y, x, img.pixel(x, y));
|
||||||
|
|
||||||
if (doReflections) {
|
if (doReflections) {
|
||||||
// create the reflection
|
// create the reflection
|
||||||
int ht = hs - h;
|
ht = hs - h;
|
||||||
for(int x = 0; x < w; x++)
|
for(x = 0; x < w; x++)
|
||||||
for(int y = 0; y < ht; y++)
|
for(y = 0; y < ht; y++)
|
||||||
{
|
{
|
||||||
QRgb color = img.pixel(x, img.height()-y-1);
|
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);
|
//QRgb565 color = img.scanLine(img.height()-y-1) + x*sizeof(QRgb565); //img.pixel(x, img.height()-y-1);
|
||||||
int a = qAlpha(color);
|
a = qAlpha(color);
|
||||||
int r = qRed(color) * a / 256 * (ht - y) / ht * 3/5;
|
r = qRed(color) * a / 256 * (ht - y) / ht * 3/5;
|
||||||
int g = qGreen(color) * a / 256 * (ht - y) / ht * 3/5;
|
g = qGreen(color) * a / 256 * (ht - y) / ht * 3/5;
|
||||||
int b = qBlue(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));
|
result.setPixel(h+y, x, qRgb(r, g, b));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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;
|
||||||
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
@ -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);
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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 &fullscreen</string>
|
<string>When showing cover browser in separate window, show it &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 &aspect ratio of covers displayed in the cover browser</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user