2020-11-18 20:30:22 +00:00
-- plugin for saving a cover image to a file and scale it to fit the screen
2020-11-10 14:00:56 +00:00
local Device = require ( " device " )
2020-11-28 12:58:44 +00:00
if not ( Device.isAndroid ( ) or Device.isEmulator ( ) or Device.isRemarkable ( ) or Device.isPocketBook ( ) ) then
2020-11-10 14:00:56 +00:00
return { disabled = true }
end
2020-11-18 20:30:22 +00:00
local Blitbuffer = require ( " ffi/blitbuffer " )
2020-11-10 14:00:56 +00:00
local InfoMessage = require ( " ui/widget/infomessage " )
local UIManager = require ( " ui/uimanager " )
local WidgetContainer = require ( " ui/widget/container/widgetcontainer " )
2020-11-18 20:30:22 +00:00
local RenderImage = require ( " ui/renderimage " )
2020-11-10 14:00:56 +00:00
local ffiutil = require ( " ffi/util " )
local lfs = require ( " libs/libkoreader-lfs " )
local logger = require ( " logger " )
local util = require ( " util " )
local _ = require ( " gettext " )
local T = require ( " ffi/util " ) . template
local function pathOk ( filename )
local path , name = util.splitFilePathName ( filename )
if not Device : isValidPath ( path ) then -- isValidPath expects a trailing slash
return false , T ( _ ( " Path \" %1 \" isn't in a writable location. " ) , path )
elseif not util.pathExists ( path : gsub ( " /$ " , " " ) ) then -- pathExists expects no trailing slash
return false , T ( _ ( " The path \" %1 \" doesn't exist. " ) , path )
elseif name == " " then
return false , _ ( " Please enter a filename at the end of the path. " )
elseif lfs.attributes ( filename , " mode " ) == " directory " then
return false , T ( _ ( " The path \" %1 \" must point to a file, but it points to a directory. " ) , filename )
end
return true
end
2020-11-28 12:57:33 +00:00
local function getExtension ( filename )
local _ , name = util.splitFilePathName ( filename )
return util.getFileNameSuffix ( name ) : lower ( )
end
2020-11-10 14:00:56 +00:00
local CoverImage = WidgetContainer : new {
name = " coverimage " ,
is_doc_only = true ,
}
function CoverImage : init ( )
2020-12-05 22:54:06 +00:00
self.cover_image_path = G_reader_settings : readSetting ( " cover_image_path " ) or " cover.jpg "
2020-11-28 12:57:33 +00:00
self.cover_image_format = G_reader_settings : readSetting ( " cover_image_format " ) or " auto "
self.cover_image_extension = getExtension ( self.cover_image_path )
2020-12-05 22:54:06 +00:00
self.cover_image_quality = G_reader_settings : readSetting ( " cover_image_quality " ) or 75
2021-01-31 20:34:37 +00:00
self.cover_image_stretch_limit = G_reader_settings : readSetting ( " cover_image_stretch_limit " ) or 5
2020-11-18 20:30:22 +00:00
self.cover_image_background = G_reader_settings : readSetting ( " cover_image_background " ) or " black "
2020-11-10 14:00:56 +00:00
self.cover_image_fallback_path = G_reader_settings : readSetting ( " cover_image_fallback_path " ) or " cover_fallback.png "
self.enabled = G_reader_settings : isTrue ( " cover_image_enabled " )
self.fallback = G_reader_settings : isTrue ( " cover_image_fallback " )
self.ui . menu : registerToMainMenu ( self )
2020-11-18 20:30:22 +00:00
2020-11-10 14:00:56 +00:00
end
function CoverImage : _enabled ( )
return self.enabled
end
function CoverImage : _fallback ( )
return self.fallback
end
function CoverImage : cleanUpImage ( )
if self.cover_image_fallback_path == " " or not self.fallback then
os.remove ( self.cover_image_path )
elseif lfs.attributes ( self.cover_image_fallback_path , " mode " ) ~= " file " then
UIManager : show ( InfoMessage : new {
2020-11-12 20:54:42 +00:00
text = T ( _ ( " \" %1 \" \n is not a valid image file! \n A valid fallback image is required in Cover-Image. " ) , self.cover_image_fallback_path ) ,
2020-11-10 14:00:56 +00:00
show_icon = true ,
timeout = 10 ,
} )
os.remove ( self.cover_image_path )
elseif pathOk ( self.cover_image_path ) then
ffiutil.copyFile ( self.cover_image_fallback_path , self.cover_image_path )
end
end
function CoverImage : createCoverImage ( doc_settings )
if self.enabled and not doc_settings : readSetting ( " exclude_cover_image " ) == true then
2020-11-18 20:30:22 +00:00
local cover_image = self.ui . document : getCoverPageImage ( )
if cover_image then
local s_w , s_h = Device.screen : getWidth ( ) , Device.screen : getHeight ( )
local i_w , i_h = cover_image : getWidth ( ) , cover_image : getHeight ( )
local scale_factor = math.min ( s_w / i_w , s_h / i_h )
if self.cover_image_background == " none " or scale_factor == 1 then
2020-11-28 12:57:33 +00:00
local act_format = self.cover_image_format == " auto " and self.cover_image_extension or self.cover_image_format
2020-12-05 22:54:06 +00:00
if not cover_image : writeToFile ( self.cover_image_path , act_format , self.cover_image_quality ) then
UIManager : show ( InfoMessage : new {
2020-12-12 20:04:58 +00:00
text = _ ( " Error writing file " ) .. " \n " .. self.cover_image_path ,
2020-12-05 22:54:06 +00:00
show_icon = true ,
} )
end
2020-11-18 20:30:22 +00:00
cover_image : free ( )
return
end
2021-01-31 20:34:37 +00:00
local screen_ratio = s_w / s_h
local image_ratio = i_w / i_h
local ratio_divergence_percent = math.abs ( 100 - image_ratio / screen_ratio * 100 )
2020-11-18 20:30:22 +00:00
2021-01-31 20:34:37 +00:00
logger.dbg ( " CoverImage: geometries screen= " .. screen_ratio .. " , image= " .. image_ratio .. " ; ratio= " .. ratio_divergence_percent )
local image
if ratio_divergence_percent < self.cover_image_stretch_limit then -- stretch
logger.dbg ( " CoverImage: stretch to fullscreen " )
image = RenderImage : scaleBlitBuffer ( cover_image , s_w , s_h )
else -- scale
local scaled_w , scaled_h = math.floor ( i_w * scale_factor ) , math.floor ( i_h * scale_factor )
logger.dbg ( " CoverImage: scale to fullscreen, fill background " )
2020-11-18 20:30:22 +00:00
2021-01-31 20:34:37 +00:00
cover_image = RenderImage : scaleBlitBuffer ( cover_image , scaled_w , scaled_h )
-- new buffer with screen dimensions,
image = Blitbuffer.new ( s_w , s_h , cover_image : getType ( ) ) -- new buffer, filled with black
if self.cover_image_background == " white " then
image : fill ( Blitbuffer.COLOR_WHITE )
elseif self.cover_image_background == " gray " then
image : fill ( Blitbuffer.COLOR_GRAY )
end
-- copy scaled image to buffer
if s_w > scaled_w then -- move right
image : blitFrom ( cover_image , math.floor ( ( s_w - scaled_w ) / 2 ) , 0 , 0 , 0 , scaled_w , scaled_h )
else -- move down
image : blitFrom ( cover_image , 0 , math.floor ( ( s_h - scaled_h ) / 2 ) , 0 , 0 , scaled_w , scaled_h )
end
2020-11-18 20:30:22 +00:00
end
2021-01-31 20:34:37 +00:00
2020-11-18 20:30:22 +00:00
cover_image : free ( )
2020-11-28 12:57:33 +00:00
local act_format = self.cover_image_format == " auto " and self.cover_image_extension or self.cover_image_format
2020-12-05 22:54:06 +00:00
if not image : writeToFile ( self.cover_image_path , act_format , self.cover_image_quality ) then
UIManager : show ( InfoMessage : new {
2020-12-12 20:04:58 +00:00
text = _ ( " Error writing file " ) .. " \n " .. self.cover_image_path ,
2020-12-05 22:54:06 +00:00
show_icon = true ,
} )
end
2020-11-28 12:57:33 +00:00
2020-11-18 20:30:22 +00:00
image : free ( )
2020-11-10 14:00:56 +00:00
logger.dbg ( " CoverImage: image written to " .. self.cover_image_path )
end
end
end
function CoverImage : onCloseDocument ( )
logger.dbg ( " CoverImage: onCloseDocument " )
if self.fallback then
self : cleanUpImage ( )
end
end
function CoverImage : onReaderReady ( doc_settings )
logger.dbg ( " CoverImage: onReaderReady " )
self : createCoverImage ( doc_settings )
end
local about_text = _ ( [ [
This plugin saves the current book cover to a file . That file can be used as a screensaver on certain Android devices , such as Tolinos .
If enabled , the cover image of the actual file is stored in the selected screensaver file . Books can be excluded if desired .
If fallback is activated , the fallback file will be copied to the screensaver file on book closing .
If the filename is empty or the file doesn ' t exist, the cover file will be deleted and the system screensaver will be used instead.
If the fallback image isn ' t activated, the screensaver image will stay in place after closing a book.]])
function CoverImage : addToMainMenu ( menu_items )
menu_items.coverimage = {
-- sorting_hint = "document",
sorting_hint = " screen " ,
text = _ ( " Save cover image " ) ,
checked_func = function ( )
return self.enabled or self.fallback
end ,
sub_item_table = {
-- menu entry: about cover image
{
text = _ ( " About cover image " ) ,
keep_menu_open = true ,
callback = function ( )
UIManager : show ( InfoMessage : new {
text = about_text ,
} )
end ,
separator = true ,
} ,
-- menu entry: filename dialog
{
text = _ ( " Set system screensaver image " ) ,
checked_func = function ( )
return self.cover_image_path ~= " " and pathOk ( self.cover_image_path )
end ,
help_text = _ ( " The cover of the current book will be stored in this file. " ) ,
keep_menu_open = true ,
callback = function ( menu )
local InputDialog = require ( " ui/widget/inputdialog " )
local sample_input
sample_input = InputDialog : new {
title = _ ( " Screensaver image filename " ) ,
input = self.cover_image_path ,
input_type = " string " ,
description = _ ( " You can enter the filename of the cover image here. " ) ,
buttons = {
{
{
text = _ ( " Cancel " ) ,
callback = function ( )
UIManager : close ( sample_input )
end ,
} ,
{
text = _ ( " Save " ) ,
is_enter_default = true ,
callback = function ( )
local new_cover_image_path = sample_input : getInputText ( )
if new_cover_image_path ~= self.cover_image_path then
self : cleanUpImage ( ) -- with old filename
self.cover_image_path = new_cover_image_path -- update filename
G_reader_settings : saveSetting ( " cover_image_path " , self.cover_image_path )
local is_path_ok , is_path_ok_message = pathOk ( self.cover_image_path )
if self.cover_image_path ~= " " and is_path_ok then
self : createCoverImage ( self.ui . doc_settings ) -- with new filename
else
self.enabled = false
UIManager : show ( InfoMessage : new {
text = is_path_ok_message ,
show_icon = true ,
} )
end
end
2020-11-28 12:57:33 +00:00
self.cover_image_extension = getExtension ( self.cover_image_path )
2020-11-10 14:00:56 +00:00
UIManager : close ( sample_input )
menu : updateItems ( )
end ,
} ,
}
} ,
}
UIManager : show ( sample_input )
sample_input : onShowKeyboard ( )
end ,
} ,
-- menu entry: enable
{
text = _ ( " Save book cover " ) ,
checked_func = function ( )
return self : _enabled ( ) and pathOk ( self.cover_image_path )
end ,
enabled_func = function ( )
return self.cover_image_path ~= " " and pathOk ( self.cover_image_path )
end ,
callback = function ( )
if self.cover_image_path ~= " " then
self.enabled = not self.enabled
G_reader_settings : saveSetting ( " cover_image_enabled " , self.enabled )
if self.enabled then
self : createCoverImage ( self.ui . doc_settings )
else
self : cleanUpImage ( )
end
end
end ,
} ,
2020-11-18 20:30:22 +00:00
-- menu entry: scale book cover
{
2020-11-28 12:57:33 +00:00
text = _ ( " Size, background and format " ) ,
2020-11-18 20:30:22 +00:00
enabled_func = function ( )
return self.enabled
end ,
sub_item_table = {
{
2021-01-31 20:34:37 +00:00
text_func = function ( )
return T ( _ ( " Aspect ratio stretch threshold (%1%) " ) , self.cover_image_stretch_limit )
end ,
help_text_func = function ( )
return T ( _ ( " If the image and the screen have a similar aspect ratio (±%1%), stretch the image instead of keeping its aspect ratio. " ) , self.cover_image_stretch_limit )
end ,
keep_menu_open = true ,
callback = function ( touchmenu_instance )
local old_stretch_limit = self.cover_image_stretch_limit
local SpinWidget = require ( " ui/widget/spinwidget " )
local size_spinner = SpinWidget : new {
width = math.floor ( Device.screen : getWidth ( ) * 0.6 ) ,
value = old_stretch_limit ,
value_min = 0 ,
value_max = 25 ,
default_value = 5 ,
title_text = _ ( " Set stretch threshold " ) ,
ok_text = _ ( " Set " ) ,
callback = function ( spin )
if self.enabled and spin.value ~= old_stretch_limit then
self.cover_image_stretch_limit = spin.value
G_reader_settings : saveSetting ( " cover_image_stretch_limit " , self.cover_image_stretch_limit )
self : createCoverImage ( self.ui . doc_settings )
end
if touchmenu_instance then touchmenu_instance : updateItems ( ) end
end
}
UIManager : show ( size_spinner )
if self.enabled and old_stretch_limit ~= self.cover_image_stretch_limit then
self : createCoverImage ( self.ui . doc_settings )
end
end ,
} ,
{
text = _ ( " Fit to screen, black background " ) ,
2020-11-18 20:30:22 +00:00
checked_func = function ( )
return self.cover_image_background == " black "
end ,
callback = function ( )
2020-11-28 12:57:33 +00:00
local old_background = self.cover_image_background
2020-11-18 20:30:22 +00:00
self.cover_image_background = " black "
G_reader_settings : saveSetting ( " cover_image_background " , self.cover_image_background )
2020-11-28 12:57:33 +00:00
if self.enabled and old_background ~= self.cover_image_background then
2020-11-18 20:30:22 +00:00
self : createCoverImage ( self.ui . doc_settings )
end
2020-11-28 12:57:33 +00:00
end ,
2020-11-18 20:30:22 +00:00
} ,
{
2021-01-31 20:34:37 +00:00
text = _ ( " Fit to screen, white background " ) ,
2020-11-18 20:30:22 +00:00
checked_func = function ( )
return self.cover_image_background == " white "
end ,
callback = function ( )
2020-11-28 12:57:33 +00:00
local old_background = self.cover_image_background
2020-11-18 20:30:22 +00:00
self.cover_image_background = " white "
G_reader_settings : saveSetting ( " cover_image_background " , self.cover_image_background )
2020-11-28 12:57:33 +00:00
if self.enabled and old_background ~= self.cover_image_background then
2020-11-18 20:30:22 +00:00
self : createCoverImage ( self.ui . doc_settings )
end
2020-11-28 12:57:33 +00:00
end ,
2020-11-18 20:30:22 +00:00
} ,
{
2021-01-31 20:34:37 +00:00
text = _ ( " Fit to screen, gray background " ) ,
2020-11-18 20:30:22 +00:00
checked_func = function ( )
return self.cover_image_background == " gray "
end ,
callback = function ( )
2020-11-28 12:57:33 +00:00
local old_background = self.cover_image_background
2020-11-18 20:30:22 +00:00
self.cover_image_background = " gray "
G_reader_settings : saveSetting ( " cover_image_background " , self.cover_image_background )
2020-11-28 12:57:33 +00:00
if self.enabled and old_background ~= self.cover_image_background then
2020-11-18 20:30:22 +00:00
self : createCoverImage ( self.ui . doc_settings )
end
2020-11-28 12:57:33 +00:00
end ,
2020-11-18 20:30:22 +00:00
} ,
{
text = _ ( " Original image " ) ,
checked_func = function ( )
return self.cover_image_background == " none "
end ,
callback = function ( )
2020-11-28 12:57:33 +00:00
local old_background = self.cover_image_background
2020-11-18 20:30:22 +00:00
self.cover_image_background = " none "
G_reader_settings : saveSetting ( " cover_image_background " , self.cover_image_background )
2020-11-28 12:57:33 +00:00
if self.enabled and old_background ~= self.cover_image_background then
2020-11-18 20:30:22 +00:00
self : createCoverImage ( self.ui . doc_settings )
end
2020-11-28 12:57:33 +00:00
end ,
separator = true ,
} ,
-- menu entries: File format
{
text = _ ( " File format derived from filename " ) ,
2021-01-31 20:34:37 +00:00
help_text = _ ( " If the file format is not supported, then JPG will be used. " ) ,
2020-11-28 12:57:33 +00:00
checked_func = function ( )
return self.cover_image_format == " auto "
end ,
callback = function ( )
local old_cover_image_format = self.cover_image_format
self.cover_image_format = " auto "
G_reader_settings : saveSetting ( " cover_image_format " , self.cover_image_format )
if self.enabled and old_cover_image_format ~= self.cover_image_format then
self : createCoverImage ( self.ui . doc_settings )
end
end ,
} ,
2020-12-05 22:54:06 +00:00
{
text = _ ( " JPG file format " ) ,
checked_func = function ( )
return self.cover_image_format == " jpg "
end ,
callback = function ( )
local old_cover_image_format = self.cover_image_format
self.cover_image_format = " jpg "
G_reader_settings : saveSetting ( " cover_image_format " , self.cover_image_format )
if self.enabled and old_cover_image_format ~= self.cover_image_format then
self : createCoverImage ( self.ui . doc_settings )
end
end ,
} ,
2020-11-28 12:57:33 +00:00
{
text = _ ( " PNG file format " ) ,
checked_func = function ( )
return self.cover_image_format == " png "
end ,
callback = function ( )
local old_cover_image_format = self.cover_image_format
self.cover_image_format = " png "
G_reader_settings : saveSetting ( " cover_image_format " , self.cover_image_format )
if self.enabled and old_cover_image_format ~= self.cover_image_format then
self : createCoverImage ( self.ui . doc_settings )
end
end ,
} ,
{
text = _ ( " BMP file format " ) ,
checked_func = function ( )
return self.cover_image_format == " bmp "
end ,
callback = function ( )
local old_cover_image_format = self.cover_image_format
self.cover_image_format = " bmp "
G_reader_settings : saveSetting ( " cover_image_format " , self.cover_image_format )
if self.enabled and old_cover_image_format ~= self.cover_image_format then
self : createCoverImage ( self.ui . doc_settings )
end
end ,
2020-11-18 20:30:22 +00:00
} ,
}
} ,
2020-11-10 14:00:56 +00:00
-- menu entry: exclude this cover
{
text = _ ( " Exclude this book cover " ) ,
checked_func = function ( )
return self.ui and self.ui . doc_settings and self.ui . doc_settings : readSetting ( " exclude_cover_image " ) == true
end ,
callback = function ( )
if self.ui . doc_settings : readSetting ( " exclude_cover_image " ) == true then
self.ui . doc_settings : saveSetting ( " exclude_cover_image " , false )
self : createCoverImage ( self.ui . doc_settings )
else
self.ui . doc_settings : saveSetting ( " exclude_cover_image " , true )
self : cleanUpImage ( )
end
self.ui : saveSettings ( )
end ,
separator = true ,
} ,
-- menu entry: set fallback image
{
text = _ ( " Set fallback image " ) ,
checked_func = function ( )
return lfs.attributes ( self.cover_image_fallback_path , " mode " ) == " file "
end ,
keep_menu_open = true ,
callback = function ( menu )
local InputDialog = require ( " ui/widget/inputdialog " )
local sample_input
sample_input = InputDialog : new {
title = _ ( " Fallback image filename " ) ,
input = self.cover_image_fallback_path ,
input_type = " string " ,
description = _ ( " Leave this empty to remove the cover when the document is closed. " ) ,
buttons = {
{
{
text = _ ( " Cancel " ) ,
callback = function ( )
UIManager : close ( sample_input )
end ,
} ,
{
text = _ ( " Save " ) ,
is_enter_default = true ,
callback = function ( )
self.cover_image_fallback_path = sample_input : getInputText ( )
G_reader_settings : saveSetting ( " cover_image_fallback_path " , self.cover_image_fallback_path )
2020-11-20 09:33:47 +00:00
if lfs.attributes ( self.cover_image_fallback_path , " mode " ) ~= " file "
and self.cover_image_fallback_path ~= " " then
2020-11-10 14:00:56 +00:00
UIManager : show ( InfoMessage : new {
text = T ( _ ( " \" %1 \" \n is not a valid image file! \n A valid fallback image is required in Cover-Image " ) ,
self.cover_image_fallback_path ) ,
show_icon = true ,
timeout = 10 ,
} )
end
UIManager : close ( sample_input )
menu : updateItems ( )
end ,
} ,
}
} ,
}
UIManager : show ( sample_input )
sample_input : onShowKeyboard ( )
end ,
} ,
-- menu entry: fallback
{
text = _ ( " Turn on fallback image " ) ,
checked_func = function ( )
return self : _fallback ( )
end ,
callback = function ( )
self.fallback = not self.fallback
G_reader_settings : saveSetting ( " cover_image_fallback " , self.fallback )
end ,
separator = true ,
} ,
} ,
}
end
return CoverImage