From c3024387ec5e00da4077eccf02b700e202ca634b Mon Sep 17 00:00:00 2001 From: apgrc <43510217+apgrc@users.noreply.github.com> Date: Mon, 21 Apr 2025 13:12:48 -0600 Subject: [PATCH] [CoverImage] Add rotate image option (#13604) New option to disable image rotation in cover image plugin. Fix #12308 --- plugins/coverimage.koplugin/main.lua | 178 ++++++++++++++++----------- 1 file changed, 103 insertions(+), 75 deletions(-) diff --git a/plugins/coverimage.koplugin/main.lua b/plugins/coverimage.koplugin/main.lua index 7fe5ee5e0..f77407f07 100644 --- a/plugins/coverimage.koplugin/main.lua +++ b/plugins/coverimage.koplugin/main.lua @@ -60,7 +60,7 @@ local function isFileOk(filename) end local function getExtension(filename) - local _, name = util.splitFilePathName(filename) + local dummy, name = util.splitFilePathName(filename) return util.getFileNameSuffix(name):lower() end @@ -79,6 +79,7 @@ function CoverImage:init() self.cover_image_grayscale = G_reader_settings:isTrue("cover_image_grayscale") self.cover_image_stretch_limit = G_reader_settings:readSetting("cover_image_stretch_limit", 8) self.cover_image_background = G_reader_settings:readSetting("cover_image_background", "black") + self.cover_image_rotate = G_reader_settings:readSetting("cover_image_rotate", true) self.cover_image_fallback_path = G_reader_settings:readSetting("cover_image_fallback_path", default_fallback_path) self.cover_image_cache_path = G_reader_settings:readSetting("cover_image_cache_path", @@ -110,90 +111,95 @@ function CoverImage:cleanUpImage() end function CoverImage:createCoverImage(doc_settings) - if self:coverEnabled() and doc_settings:nilOrFalse("exclude_cover_image") then - local cover_image, custom_cover = FileManagerBookInfo:getCoverImage(self.ui.document) - if cover_image then - local cache_file = self:getCacheFile(custom_cover) - if lfs.attributes(cache_file, "mode") == "file" then - logger.dbg("CoverImage: cache file already exists") - ffiutil.copyFile(cache_file, self.cover_image_path) - lfs.touch(cache_file) -- update date - return - end + if not (self:coverEnabled() and doc_settings:nilOrFalse("exclude_cover_image")) then + return + end - local s_w, s_h = Screen:getWidth(), 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) + local cover_image, custom_cover = FileManagerBookInfo:getCoverImage(self.ui.document) + if not cover_image then + return + end - if Screen:getRotationMode() == Screen.DEVICE_ROTATED_UPSIDE_DOWN - or Screen:getRotationMode() == Screen.DEVICE_ROTATED_CLOCKWISE then + local cache_file = self:getCacheFile(custom_cover) + if lfs.attributes(cache_file, "mode") == "file" then + logger.dbg("CoverImage: cache file already exists") + ffiutil.copyFile(cache_file, self.cover_image_path) + lfs.touch(cache_file) -- update date + return + end - local flipped_cover = cover_image:rotatedCopy(180) - cover_image:free() - cover_image = flipped_cover - end + local s_w, s_h = Screen:getWidth(), 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 - local act_format = self.cover_image_format == "auto" and getExtension(self.cover_image_path) or self.cover_image_format - if not cover_image:writeToFile(self.cover_image_path, act_format, self.cover_image_quality, self.cover_image_grayscale) then - UIManager:show(InfoMessage:new{ - text = _("Error writing file") .. "\n" .. self.cover_image_path, - show_icon = true, - }) - end - cover_image:free() - ffiutil.copyFile(self.cover_image_path, cache_file) - self:cleanCache() - return - end + if self.cover_image_rotate and ( + Screen:getRotationMode() == Screen.DEVICE_ROTATED_UPSIDE_DOWN or + Screen:getRotationMode() == Screen.DEVICE_ROTATED_CLOCKWISE + ) then + local rotated_cover = cover_image:rotatedCopy(180) + cover_image:free() + cover_image = rotated_cover + end - 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) + if self.cover_image_background == "none" or scale_factor == 1 then + local act_format = self.cover_image_format == "auto" and getExtension(self.cover_image_path) or self.cover_image_format + if not cover_image:writeToFile(self.cover_image_path, act_format, self.cover_image_quality, self.cover_image_grayscale) then + UIManager:show(InfoMessage:new{ + text = _("Error writing file") .. "\n" .. self.cover_image_path, + show_icon = true, + }) + end + cover_image:free() + ffiutil.copyFile(self.cover_image_path, cache_file) + self:cleanCache() + return + end - logger.dbg("CoverImage: geometries screen=" .. screen_ratio .. ", image=" .. image_ratio .. "; ratio=" .. ratio_divergence_percent) + 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) - 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") + logger.dbg("CoverImage: geometries screen=" .. screen_ratio .. ", image=" .. image_ratio .. "; ratio=" .. ratio_divergence_percent) - 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 - end + 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") - cover_image:free() - - local act_format = self.cover_image_format == "auto" and getExtension(self.cover_image_path) or self.cover_image_format - if not image:writeToFile(self.cover_image_path, act_format, self.cover_image_quality, self.cover_image_grayscale) then - UIManager:show(InfoMessage:new{ - text = _("Error writing file") .. "\n" .. self.cover_image_path, - show_icon = true, - }) - end - - image:free() - logger.dbg("CoverImage: image written to " .. self.cover_image_path) - - ffiutil.copyFile(self.cover_image_path, cache_file) - self:cleanCache() + 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 end + + cover_image:free() + + local act_format = self.cover_image_format == "auto" and getExtension(self.cover_image_path) or self.cover_image_format + if not image:writeToFile(self.cover_image_path, act_format, self.cover_image_quality, self.cover_image_grayscale) then + UIManager:show(InfoMessage:new{ + text = _("Error writing file") .. "\n" .. self.cover_image_path, + show_icon = true, + }) + end + + image:free() + logger.dbg("CoverImage: image written to " .. self.cover_image_path) + + ffiutil.copyFile(self.cover_image_path, cache_file) + self:cleanCache() end function CoverImage:onCloseDocument() @@ -228,10 +234,15 @@ end function CoverImage:getCacheFile(custom_cover) local custom_cover_mtime = custom_cover and lfs.attributes(custom_cover, "modification") or "" local dummy, document_name = util.splitFilePathName(self.ui.document.file) + local rotated = "" + if self.cover_image_rotate then + rotated = "_rotated_" + end + -- use document_name here. Title may contain characters not allowed on every filesystem (esp. vfat on /sdcard) local key = document_name .. custom_cover_mtime .. self.cover_image_quality .. self.cover_image_stretch_limit .. self.cover_image_background .. self.cover_image_format .. tostring(self.cover_image_grayscale) - .. Screen:getRotationMode() + .. Screen:getRotationMode() .. rotated return self.cover_image_cache_path .. self.cover_image_cache_prefix .. md5(key) .. "." .. getExtension(self.cover_image_path) end @@ -654,6 +665,23 @@ function CoverImage:menuEntrySBF() self:menuEntryBackground("black", _("black")), self:menuEntryBackground("white", _("white")), self:menuEntryBackground("gray", _("gray")), + { + text = _("Rotate image"), + keep_menu_open = true, + help_text_func = function() + return T(_("Rotate the generated image to follow the device orientation. Enable this when the native system does not rotate the background image to follow system orientation. Disable this if your device correctly handles screen saver rotation."), self.cover_image_stretch_limit) + end, + checked_func = function() + return self.cover_image_rotate + end, + callback = function() + self.cover_image_rotate = not self.cover_image_rotate + G_reader_settings:saveSetting("cover_image_rotate", self.cover_image_rotate) + if self:coverEnabled() then + self:createCoverImage(self.ui.doc_settings) + end + end, + }, { text = _("Original image"), checked_func = function()