2016-06-26 00:53:08 +00:00
|
|
|
--[[--
|
2021-12-01 11:37:18 +00:00
|
|
|
ImageWidget shows an image from a file or memory.
|
2016-06-26 00:53:08 +00:00
|
|
|
|
2016-11-16 09:31:39 +00:00
|
|
|
Show image from file example:
|
2016-06-26 00:53:08 +00:00
|
|
|
|
|
|
|
UIManager:show(ImageWidget:new{
|
2020-12-19 11:18:30 +00:00
|
|
|
file = "resources/koreader.png",
|
2016-06-26 00:53:08 +00:00
|
|
|
-- Make sure alpha is set to true if png has transparent background
|
|
|
|
-- alpha = true,
|
|
|
|
})
|
|
|
|
|
2016-11-16 09:31:39 +00:00
|
|
|
|
|
|
|
Show image from memory example:
|
|
|
|
|
|
|
|
UIManager:show(ImageWidget:new{
|
|
|
|
-- bitmap_buffer should be a block of memory that holds the raw
|
|
|
|
-- uncompressed bitmap.
|
|
|
|
image = bitmap_buffer,
|
|
|
|
})
|
|
|
|
|
2016-06-26 00:53:08 +00:00
|
|
|
]]
|
|
|
|
|
2019-03-14 19:58:45 +00:00
|
|
|
local Blitbuffer = require("ffi/blitbuffer")
|
2018-04-21 20:03:04 +00:00
|
|
|
local Cache = require("cache")
|
2013-10-18 20:38:07 +00:00
|
|
|
local Geom = require("ui/geometry")
|
2018-04-21 20:03:04 +00:00
|
|
|
local RenderImage = require("ui/renderimage")
|
|
|
|
local Screen = require("device").screen
|
|
|
|
local UIManager = require("ui/uimanager")
|
|
|
|
local Widget = require("ui/widget/widget")
|
2016-12-29 08:10:38 +00:00
|
|
|
local logger = require("logger")
|
2014-08-22 09:22:41 +00:00
|
|
|
|
2017-09-26 08:21:58 +00:00
|
|
|
-- DPI_SCALE can't change without a restart, so let's compute it now
|
|
|
|
local function get_dpi_scale()
|
|
|
|
local size_scale = math.min(Screen:getWidth(), Screen:getHeight())/600
|
|
|
|
local dpi_scale = Screen:getDPI() / 167
|
|
|
|
return math.pow(2, math.max(0, math.log((size_scale+dpi_scale)/2)/0.69))
|
|
|
|
end
|
|
|
|
local DPI_SCALE = get_dpi_scale()
|
|
|
|
|
2014-08-22 09:22:41 +00:00
|
|
|
local ImageCache = Cache:new{
|
2021-05-04 21:13:24 +00:00
|
|
|
-- 8 MiB of image cache, with 128 slots
|
|
|
|
-- Overwhelmingly used for our icons, which are tiny in size, and not very numerous (< 100),
|
|
|
|
-- but also by ImageViewer (on files, which we never do), and ScreenSaver (again, on image files, but not covers),
|
|
|
|
-- hence the leeway.
|
|
|
|
size = 8 * 1024 * 1024,
|
|
|
|
avg_itemsize = 64 * 1024,
|
2021-05-07 01:59:27 +00:00
|
|
|
-- Rely on our FFI finalizer to free the BBs on GC
|
|
|
|
enable_eviction_cb = false,
|
2014-08-22 09:22:41 +00:00
|
|
|
}
|
|
|
|
|
2013-10-18 20:38:07 +00:00
|
|
|
local ImageWidget = Widget:new{
|
2017-01-15 20:47:22 +00:00
|
|
|
-- Can be provided with a path to a file
|
2014-03-13 13:52:43 +00:00
|
|
|
file = nil,
|
2018-04-21 20:03:04 +00:00
|
|
|
-- or an already made BlitBuffer (ie: made by RenderImage)
|
2014-08-27 03:07:25 +00:00
|
|
|
image = nil,
|
2017-01-15 20:47:22 +00:00
|
|
|
|
|
|
|
-- Whether BlitBuffer rendered from file should be cached
|
|
|
|
file_do_cache = true,
|
|
|
|
-- Whether provided BlitBuffer can be modified by us and SHOULD be free() by us,
|
2017-09-26 08:21:58 +00:00
|
|
|
-- normally true unless our caller wants to reuse its provided image
|
2017-01-15 20:47:22 +00:00
|
|
|
image_disposable = true,
|
|
|
|
|
2017-01-26 07:56:24 +00:00
|
|
|
-- Width and height of container, to limit rendering to this area
|
|
|
|
-- (if provided, and scale_factor is nil, image will be resized to
|
|
|
|
-- these width and height without regards to original aspect ratio)
|
2014-08-14 10:08:52 +00:00
|
|
|
width = nil,
|
|
|
|
height = nil,
|
2017-01-26 07:56:24 +00:00
|
|
|
|
|
|
|
hide = nil, -- to not be painted
|
|
|
|
|
|
|
|
-- Settings that apply at paintTo() time
|
|
|
|
invert = nil,
|
|
|
|
dim = nil,
|
|
|
|
alpha = false, -- honors alpha values from the image
|
2020-12-19 11:18:30 +00:00
|
|
|
is_icon = false, -- set to true by sub-class IconWidget
|
2017-01-26 07:56:24 +00:00
|
|
|
|
|
|
|
-- When rotation_angle is not 0, native image is rotated by this angle
|
|
|
|
-- before scaling.
|
|
|
|
rotation_angle = 0,
|
|
|
|
|
|
|
|
-- If scale_for_dpi is true image will be rescaled according to screen dpi
|
|
|
|
scale_for_dpi = false,
|
|
|
|
|
2022-01-02 22:13:19 +00:00
|
|
|
-- When scale_factor is not nil, native image is scaled by this factor,
|
|
|
|
-- (if scale_factor == 1, native image size is kept)
|
|
|
|
-- Special case: scale_factor == 0 : image will be scaled to best fit provided
|
|
|
|
-- width and height, keeping aspect ratio (scale_factor will be updated
|
|
|
|
-- from 0 to the factor used at _render() time)
|
|
|
|
-- If scale_factor is nil and stretch_limit_percantage is provided:
|
|
|
|
-- If the aspect ratios of the image and the width/height provided don't differ by more than
|
|
|
|
-- stretch_limit_percentage, then stretch the image (as scale_factor=nil);
|
|
|
|
-- otherwise, scale to best fit (as scale_factor=0)
|
|
|
|
-- In all other cases the image will be stretched to best fit the container.
|
2017-01-26 07:56:24 +00:00
|
|
|
scale_factor = nil,
|
2022-01-02 22:13:19 +00:00
|
|
|
stretch_limit_percentage = nil,
|
2017-01-26 07:56:24 +00:00
|
|
|
|
2017-09-26 08:21:58 +00:00
|
|
|
-- Whether to use former blitbuffer:scale() (default to using MuPDF)
|
|
|
|
use_legacy_image_scaling = G_reader_settings:isTrue("legacy_image_scaling"),
|
|
|
|
|
2020-10-31 09:20:02 +00:00
|
|
|
-- For initial positioning, if (possibly scaled) image overflows width/height
|
2017-01-26 07:56:24 +00:00
|
|
|
center_x_ratio = 0.5, -- default is centered on image's center
|
|
|
|
center_y_ratio = 0.5,
|
|
|
|
|
|
|
|
-- For pan & zoom management:
|
|
|
|
-- offsets to use in blitFrom()
|
|
|
|
_offset_x = 0,
|
|
|
|
_offset_y = 0,
|
|
|
|
-- limits to center_x_ratio variation around 0.5 (0.5 +/- these values)
|
|
|
|
-- to keep image centered (0 means center_x_ratio will be forced to 0.5)
|
|
|
|
_max_off_center_x_ratio = 0,
|
|
|
|
_max_off_center_y_ratio = 0,
|
|
|
|
|
|
|
|
-- So we can reset self.scale_factor to its initial value in free(), in
|
|
|
|
-- case this same object is free'd but re-used and and re-render'ed
|
|
|
|
_initial_scale_factor = nil,
|
2017-01-15 20:47:22 +00:00
|
|
|
|
|
|
|
_bb = nil,
|
2017-01-26 07:56:24 +00:00
|
|
|
_bb_disposable = true, -- whether we should free() our _bb
|
|
|
|
_bb_w = nil,
|
|
|
|
_bb_h = nil,
|
2013-03-12 17:18:53 +00:00
|
|
|
}
|
|
|
|
|
2014-08-27 03:07:25 +00:00
|
|
|
function ImageWidget:_loadimage()
|
|
|
|
self._bb = self.image
|
2017-01-15 20:47:22 +00:00
|
|
|
-- don't touch or free if caller doesn't want that
|
|
|
|
self._bb_disposable = self.image_disposable
|
2014-08-27 03:07:25 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function ImageWidget:_loadfile()
|
2014-03-13 13:52:43 +00:00
|
|
|
local itype = string.lower(string.match(self.file, ".+%.([^.]+)") or "")
|
2020-12-05 22:57:00 +00:00
|
|
|
if itype == "svg" or itype == "png" or itype == "jpg" or itype == "jpeg"
|
|
|
|
or itype == "gif" or itype == "tiff" or itype == "tif" then
|
2017-09-26 08:21:58 +00:00
|
|
|
-- In our use cases for files (icons), we either provide width and height,
|
|
|
|
-- or just scale_for_dpi, and scale_factor should stay nil.
|
2018-01-21 18:44:12 +00:00
|
|
|
-- Other combinations will result in double scaling, and unexpected results.
|
2018-04-21 20:03:04 +00:00
|
|
|
-- We should anyway only give self.width and self.height to renderImageFile(),
|
2018-01-21 18:44:12 +00:00
|
|
|
-- and use them in cache hash, when self.scale_factor is nil, when we are sure
|
|
|
|
-- we don't need to keep aspect ratio.
|
|
|
|
local width, height
|
|
|
|
if self.scale_factor == nil then
|
|
|
|
width = self.width
|
|
|
|
height = self.height
|
|
|
|
end
|
|
|
|
local hash = "image|"..self.file.."|"..(width or "").."|"..(height or "")
|
|
|
|
-- Do the scaling for DPI here, so it can be cached and not re-done
|
2018-07-19 06:18:55 +00:00
|
|
|
-- each time in _render() (but not if scale_factor, to avoid double scaling)
|
2017-09-26 08:21:58 +00:00
|
|
|
local scale_for_dpi_here = false
|
2018-07-19 06:18:55 +00:00
|
|
|
if self.scale_for_dpi and DPI_SCALE ~= 1 and not self.scale_factor then
|
2017-09-26 08:21:58 +00:00
|
|
|
scale_for_dpi_here = true -- we'll do it before caching
|
|
|
|
hash = hash .. "|d"
|
|
|
|
self.already_scaled_for_dpi = true -- so we don't do it again in _render()
|
|
|
|
end
|
2021-05-07 01:59:27 +00:00
|
|
|
local cached = ImageCache:check(hash)
|
|
|
|
if cached then
|
2014-08-22 09:22:41 +00:00
|
|
|
-- hit cache
|
2021-05-07 01:59:27 +00:00
|
|
|
self._bb = cached.bb
|
2017-01-15 20:47:22 +00:00
|
|
|
self._bb_disposable = false -- don't touch or free a cached _bb
|
2021-05-07 01:59:27 +00:00
|
|
|
self._is_straight_alpha = cached.is_straight_alpha
|
2014-08-22 09:22:41 +00:00
|
|
|
else
|
2020-12-05 22:57:00 +00:00
|
|
|
if itype == "svg" then
|
|
|
|
local zoom
|
|
|
|
if scale_for_dpi_here then
|
|
|
|
zoom = DPI_SCALE
|
|
|
|
elseif self.scale_factor == 0 then
|
|
|
|
-- renderSVGImageFile() keeps aspect ratio by default
|
|
|
|
width = self.width
|
|
|
|
height = self.height
|
|
|
|
end
|
2020-12-19 11:18:30 +00:00
|
|
|
-- If NanoSVG is used by renderSVGImageFile, we'll get self._is_straight_alpha=true,
|
|
|
|
-- and paintTo() must use alphablitFrom() instead of pmulalphablitFrom() (which is
|
|
|
|
-- fine for everything MuPDF renders out)
|
|
|
|
self._bb, self._is_straight_alpha = RenderImage:renderSVGImageFile(self.file, width, height, zoom)
|
2020-12-05 22:57:00 +00:00
|
|
|
else
|
|
|
|
self._bb = RenderImage:renderImageFile(self.file, false, width, height)
|
|
|
|
if scale_for_dpi_here then
|
|
|
|
local bb_w, bb_h = self._bb:getWidth(), self._bb:getHeight()
|
|
|
|
self._bb = RenderImage:scaleBlitBuffer(self._bb, math.floor(bb_w * DPI_SCALE), math.floor(bb_h * DPI_SCALE))
|
|
|
|
end
|
2017-09-26 08:21:58 +00:00
|
|
|
end
|
2020-12-19 21:34:16 +00:00
|
|
|
|
Page Overlap: Fix rectangle computation and arrow mode (#7269)
* In ReaderPaging, the panning step pre-PanningUpdate can be wildly overshot near page edges, so, use the corrected value instead by recomputing it after the panning has been effectively computed by ReaderView.
This fixes slight inaccuracies, as well as glaring mistakes when going backwards, or when near page edges.
This is in line with how ReaderRolling computes the value, which I only realized later because I'm an idiot.
* Minor cleanups around the handling of the dim_area Geom object in general.
* Fix the "Arrow" page overlap mode to be painted in the right coordinates when going backward. Issue might not have been terribly clear because of the previous issue ;).
* Center the arrow's point, while we're here.
* Don't use AlphaContainer to make it translucent, because AlphaContainer is horribly broken, and has weird quirks and behavior that make no sense to me unless some very specific and unlikely constraints are met, and they definitely aren't here.
This fixes the arrow copying an arrow-sized square of the original page the book was opened on on the top-left corner of *every* page with an arrow. (lol).
* Do real proper alpha-blending via Icon/ImageWidget from the original icon, instead of faking it via addBlitFrom, in order to avoid the dimming *around* the triangle's shape.
2021-02-10 19:06:41 +00:00
|
|
|
-- Now, if that was *also* one of our icons, we haven't explicitly requested to keep the alpha channel intact,
|
|
|
|
-- and it actually has an alpha channel, compose it against a background-colored BB now, and cache *that*.
|
2020-12-19 21:34:16 +00:00
|
|
|
-- This helps us avoid repeating alpha-blending steps down the line,
|
Revamp "flash_ui" handling (#7118)
* Wherever possible, do an actual dumb invert on the Screen BB instead of repainting the widget, *then* inverting it (which is what the "invert" flag does).
* Instead of playing with nextTick/tickAfterNext delays, explicitly fence stuff with forceRePaint
* And, in the few cases where said Mk. 7 quirk kicks in, make the fences more marked by using a well-placed WAIT_FOR_UPDATE_COMPLETE
* Fix an issue in Button where show/hide & enable/disable where actually all toggles, which meant that duplicate calls or timing issues would do the wrong thing. (This broke dimming some icons, and mistakenly dropped the background from FM chevrons, for example).
* Speaking of, fix Button's hide/show to actually restore the background properly (there was a stupid typo in the variable name)
* Still in Button, fix the insanity of the double repaint on rounded buttons. Turns out it made sense, after all (and was related to said missing background, and bad interaction with invert & text with no background).
* KeyValuePage suffered from a similar issue with broken highlights (all black) because of missing backgrounds.
* In ConfigDialog, only instanciate IconButtons once (because every tab switch causes a full instantiation; and the initial display implies a full instanciation and an initial tab switch). Otherwise, both instances linger, and catch taps, and as such, double highlights.
* ConfigDialog: Restore the "don't repaint ReaderUI" when switching between similarly sized tabs (re #6131). I never could reproduce that on eInk, and I can't now on the emulator, so I'm assuming @poire-z fixed it during the swap to SVG icons.
* KeyValuePage: Only instanciate Buttons once (again, this is a widget that goes through a full init every page). Again, caused highlight/dimming issues because buttons were stacked.
* Menu: Ditto.
* TouchMenu: Now home of the gnarliest unhilight heuristics, because of the sheer amount of different things that can happen (and/or thanks to stuff not flagged covers_fullscreen properly ;p).
* Bump base
https://github.com/koreader/koreader-base/pull/1280
https://github.com/koreader/koreader-base/pull/1282
https://github.com/koreader/koreader-base/pull/1283
https://github.com/koreader/koreader-base/pull/1284
* Bump android-luajit-launcher
https://github.com/koreader/android-luajit-launcher/pull/284
https://github.com/koreader/android-luajit-launcher/pull/285
https://github.com/koreader/android-luajit-launcher/pull/286
https://github.com/koreader/android-luajit-launcher/pull/287
2021-01-10 00:51:09 +00:00
|
|
|
-- and also ensures icon highlights/unhighlights behave sensibly.
|
Page Overlap: Fix rectangle computation and arrow mode (#7269)
* In ReaderPaging, the panning step pre-PanningUpdate can be wildly overshot near page edges, so, use the corrected value instead by recomputing it after the panning has been effectively computed by ReaderView.
This fixes slight inaccuracies, as well as glaring mistakes when going backwards, or when near page edges.
This is in line with how ReaderRolling computes the value, which I only realized later because I'm an idiot.
* Minor cleanups around the handling of the dim_area Geom object in general.
* Fix the "Arrow" page overlap mode to be painted in the right coordinates when going backward. Issue might not have been terribly clear because of the previous issue ;).
* Center the arrow's point, while we're here.
* Don't use AlphaContainer to make it translucent, because AlphaContainer is horribly broken, and has weird quirks and behavior that make no sense to me unless some very specific and unlikely constraints are met, and they definitely aren't here.
This fixes the arrow copying an arrow-sized square of the original page the book was opened on on the top-left corner of *every* page with an arrow. (lol).
* Do real proper alpha-blending via Icon/ImageWidget from the original icon, instead of faking it via addBlitFrom, in order to avoid the dimming *around* the triangle's shape.
2021-02-10 19:06:41 +00:00
|
|
|
if self.is_icon and not self.alpha then
|
2020-12-19 21:34:16 +00:00
|
|
|
local bbtype = self._bb:getType()
|
|
|
|
if bbtype == Blitbuffer.TYPE_BB8A or bbtype == Blitbuffer.TYPE_BBRGB32 then
|
|
|
|
local icon_bb = Blitbuffer.new(self._bb.w, self._bb.h, Screen.bb:getType())
|
|
|
|
--- @note: Should match the background color. Which is currently hard-coded as white ;).
|
|
|
|
--- See the note below in paintTo for how to make the dim flag behave in case
|
|
|
|
--- this no longer actually is white ;).
|
|
|
|
icon_bb:fill(Blitbuffer.COLOR_WHITE)
|
|
|
|
|
|
|
|
-- And now simply compose the icon on top of that, with dithering if necessary
|
|
|
|
-- Remembering that NanoSVG feeds us straight alpha, unlike MµPDF
|
|
|
|
if self._is_straight_alpha then
|
|
|
|
if Screen.sw_dithering then
|
|
|
|
icon_bb:ditheralphablitFrom(self._bb, 0, 0, 0, 0, icon_bb.w, icon_bb.h)
|
|
|
|
else
|
|
|
|
icon_bb:alphablitFrom(self._bb, 0, 0, 0, 0, icon_bb.w, icon_bb.h)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if Screen.sw_dithering then
|
|
|
|
icon_bb:ditherpmulalphablitFrom(self._bb, 0, 0, 0, 0, icon_bb.w, icon_bb.h)
|
|
|
|
else
|
|
|
|
icon_bb:pmulalphablitFrom(self._bb, 0, 0, 0, 0, icon_bb.w, icon_bb.h)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Free the original icon w/ an alpha-channel, keep the flattened one
|
|
|
|
self._bb:free()
|
|
|
|
self._bb = icon_bb
|
|
|
|
|
|
|
|
-- There's no longer an alpha channel ;)
|
|
|
|
self._is_straight_alpha = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-01-15 20:47:22 +00:00
|
|
|
if not self.file_do_cache then
|
|
|
|
self._bb_disposable = true -- we made it, we can modify and free it
|
2016-12-04 14:22:05 +00:00
|
|
|
else
|
2017-09-26 08:21:58 +00:00
|
|
|
self._bb_disposable = false -- don't touch or free a cached _bb
|
2016-12-04 14:22:05 +00:00
|
|
|
-- cache this image
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("cache", hash)
|
2021-05-07 01:59:27 +00:00
|
|
|
cached = {
|
2020-12-19 11:18:30 +00:00
|
|
|
bb = self._bb,
|
|
|
|
is_straight_alpha = self._is_straight_alpha,
|
|
|
|
}
|
2021-05-07 01:59:27 +00:00
|
|
|
ImageCache:insert(hash, cached, tonumber(cached.bb.stride) * cached.bb.h)
|
2016-12-04 14:22:05 +00:00
|
|
|
end
|
2014-08-22 09:22:41 +00:00
|
|
|
end
|
2014-08-17 09:39:03 +00:00
|
|
|
else
|
|
|
|
error("Image file type not supported.")
|
2014-03-13 13:52:43 +00:00
|
|
|
end
|
2014-08-27 03:07:25 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function ImageWidget:_render()
|
2016-12-04 14:22:05 +00:00
|
|
|
if self._bb then -- already rendered
|
|
|
|
return
|
|
|
|
end
|
2017-10-21 17:53:56 +00:00
|
|
|
logger.dbg("ImageWidget: _render'ing", self.file and self.file or "data", self.width, self.height)
|
2014-08-27 03:07:25 +00:00
|
|
|
if self.image then
|
|
|
|
self:_loadimage()
|
|
|
|
elseif self.file then
|
|
|
|
self:_loadfile()
|
|
|
|
else
|
|
|
|
error("cannot render image")
|
|
|
|
end
|
2017-01-26 07:56:24 +00:00
|
|
|
|
|
|
|
-- Store initial scale factor
|
|
|
|
self._initial_scale_factor = self.scale_factor
|
|
|
|
|
|
|
|
-- First, rotation
|
|
|
|
if self.rotation_angle ~= 0 then
|
2017-09-26 08:21:58 +00:00
|
|
|
-- Allow for easy switch to former scaling via blitbuffer methods
|
|
|
|
if self.use_legacy_image_scaling then
|
|
|
|
if not self._bb_disposable then
|
|
|
|
-- we can't modify _bb, make a copy
|
|
|
|
self._bb = self._bb:copy()
|
|
|
|
self._bb_disposable = true -- new object will have to be freed
|
|
|
|
end
|
|
|
|
self._bb:rotate(self.rotation_angle) -- rotate in-place
|
|
|
|
else
|
|
|
|
-- If we use MuPDF for scaling, we can't use bb:rotate() anymore,
|
|
|
|
-- as it only flags rotation in the blitbuffer and rotation is dealt
|
|
|
|
-- with at painting time. MuPDF does not like such a blitbuffer, and
|
|
|
|
-- we get corrupted images when using it for scaling such blitbuffers.
|
|
|
|
-- We need to make a real new blitbuffer with rotated content:
|
|
|
|
local rot_bb = self._bb:rotatedCopy(self.rotation_angle)
|
|
|
|
-- We made a new blitbuffer, we need to explicitely free
|
|
|
|
-- the old one to not leak memory
|
|
|
|
if self._bb_disposable then
|
|
|
|
self._bb:free()
|
|
|
|
end
|
|
|
|
self._bb = rot_bb
|
2017-01-15 20:47:22 +00:00
|
|
|
self._bb_disposable = true -- new object will have to be freed
|
|
|
|
end
|
|
|
|
end
|
2017-01-26 07:56:24 +00:00
|
|
|
|
|
|
|
local bb_w, bb_h = self._bb:getWidth(), self._bb:getHeight()
|
|
|
|
|
|
|
|
-- scale_for_dpi setting: update scale_factor (even if not set) with it
|
2017-09-26 08:21:58 +00:00
|
|
|
if self.scale_for_dpi and not self.already_scaled_for_dpi then
|
2017-01-26 07:56:24 +00:00
|
|
|
if self.scale_factor == nil then
|
|
|
|
self.scale_factor = 1
|
2016-04-15 00:42:54 +00:00
|
|
|
end
|
2017-09-26 08:21:58 +00:00
|
|
|
self.scale_factor = self.scale_factor * DPI_SCALE
|
2014-11-20 07:52:05 +00:00
|
|
|
end
|
2017-01-26 07:56:24 +00:00
|
|
|
|
2022-01-02 22:13:19 +00:00
|
|
|
if self.stretch_limit_percentage and not self.scale_factor then
|
|
|
|
-- stretch or scale to fit container, depending on self.stretch_limit_percentage
|
|
|
|
local screen_ratio = self.width / self.height
|
|
|
|
local image_ratio = bb_w / bb_h
|
|
|
|
local ratio_divergence_percent = math.abs(100 - image_ratio / screen_ratio * 100)
|
|
|
|
if ratio_divergence_percent > self.stretch_limit_percentage then
|
|
|
|
self.scale_factor = 0
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-01-26 07:56:24 +00:00
|
|
|
if self.scale_factor == 0 then
|
2022-01-02 22:13:19 +00:00
|
|
|
-- scale to best fit container: compute scale_factor for that
|
2017-01-26 07:56:24 +00:00
|
|
|
if self.width and self.height then
|
|
|
|
self.scale_factor = math.min(self.width / bb_w, self.height / bb_h)
|
|
|
|
logger.dbg("ImageWidget: scale to fit, setting scale_factor to", self.scale_factor)
|
|
|
|
else
|
|
|
|
-- no width and height provided (inconsistencies from caller),
|
|
|
|
self.scale_factor = 1 -- native image size
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-09-26 08:21:58 +00:00
|
|
|
-- replace blitbuffer with a resized one if needed
|
2017-01-26 07:56:24 +00:00
|
|
|
if self.scale_factor == nil then
|
2017-09-24 13:22:06 +00:00
|
|
|
-- no scaling, but strech to width and height, only if provided and needed
|
|
|
|
if self.width and self.height and (self.width ~= bb_w or self.height ~= bb_h) then
|
2017-01-26 07:56:24 +00:00
|
|
|
logger.dbg("ImageWidget: stretching")
|
2018-04-21 20:03:04 +00:00
|
|
|
self._bb = RenderImage:scaleBlitBuffer(self._bb, self.width, self.height, self._bb_disposable)
|
|
|
|
self._bb_disposable = true -- new bb will have to be freed
|
2017-01-26 07:56:24 +00:00
|
|
|
end
|
|
|
|
elseif self.scale_factor ~= 1 then
|
|
|
|
-- scale by scale_factor (not needed if scale_factor == 1)
|
|
|
|
logger.dbg("ImageWidget: scaling by", self.scale_factor)
|
2018-04-21 20:03:04 +00:00
|
|
|
self._bb = RenderImage:scaleBlitBuffer(self._bb, bb_w * self.scale_factor, bb_h * self.scale_factor, self._bb_disposable)
|
|
|
|
self._bb_disposable = true -- new bb will have to be freed
|
2017-01-26 07:56:24 +00:00
|
|
|
end
|
2018-04-21 20:03:04 +00:00
|
|
|
bb_w, bb_h = self._bb:getWidth(), self._bb:getHeight()
|
2017-01-26 07:56:24 +00:00
|
|
|
|
2020-10-31 09:20:02 +00:00
|
|
|
-- deal with positioning
|
2017-01-26 07:56:24 +00:00
|
|
|
if self.width and self.height then
|
|
|
|
-- if image is bigger than paint area, allow center_ratio variation
|
|
|
|
-- around 0.5 so we can pan till image border
|
|
|
|
if bb_w > self.width then
|
|
|
|
self._max_off_center_x_ratio = 0.5 - self.width/2 / bb_w
|
|
|
|
end
|
|
|
|
if bb_h > self.height then
|
|
|
|
self._max_off_center_y_ratio = 0.5 - self.height/2 / bb_h
|
|
|
|
end
|
|
|
|
-- correct provided center ratio if out limits
|
|
|
|
if self.center_x_ratio < 0.5 - self._max_off_center_x_ratio then
|
|
|
|
self.center_x_ratio = 0.5 - self._max_off_center_x_ratio
|
|
|
|
elseif self.center_x_ratio > 0.5 + self._max_off_center_x_ratio then
|
|
|
|
self.center_x_ratio = 0.5 + self._max_off_center_x_ratio
|
|
|
|
end
|
|
|
|
if self.center_y_ratio < 0.5 - self._max_off_center_y_ratio then
|
|
|
|
self.center_y_ratio = 0.5 - self._max_off_center_y_ratio
|
|
|
|
elseif self.center_y_ratio > 0.5 + self._max_off_center_y_ratio then
|
|
|
|
self.center_y_ratio = 0.5 + self._max_off_center_y_ratio
|
|
|
|
end
|
|
|
|
-- set offsets to reflect center ratio, whether oversized or not
|
|
|
|
self._offset_x = self.center_x_ratio * bb_w - self.width/2
|
|
|
|
self._offset_y = self.center_y_ratio * bb_h - self.height/2
|
|
|
|
logger.dbg("ImageWidget: initial offsets", self._offset_x, self._offset_y)
|
2014-08-14 10:08:52 +00:00
|
|
|
end
|
2017-01-26 07:56:24 +00:00
|
|
|
|
|
|
|
-- store final bb's width and height
|
|
|
|
self._bb_w = bb_w
|
|
|
|
self._bb_h = bb_h
|
2013-03-12 17:18:53 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function ImageWidget:getSize()
|
2014-08-22 09:22:41 +00:00
|
|
|
self:_render()
|
2017-01-26 07:56:24 +00:00
|
|
|
-- getSize will be used by the widget stack for centering/padding
|
|
|
|
if not self.width or not self.height then
|
|
|
|
-- no width/height provided, return bb size to let widget stack do the centering
|
|
|
|
return Geom:new{ w = self._bb:getWidth(), h = self._bb:getHeight() }
|
|
|
|
end
|
|
|
|
-- if width or height provided, return them as is, even if image is smaller
|
|
|
|
-- and would be centered: we'll do the centering ourselves with offsets
|
|
|
|
return Geom:new{ w = self.width, h = self.height }
|
2013-03-12 17:18:53 +00:00
|
|
|
end
|
|
|
|
|
2017-01-26 07:56:24 +00:00
|
|
|
function ImageWidget:getScaleFactor()
|
|
|
|
-- return computed scale_factor, useful if 0 (scale to fit) was used
|
|
|
|
return self.scale_factor
|
|
|
|
end
|
|
|
|
|
|
|
|
function ImageWidget:getPanByCenterRatio(x, y)
|
|
|
|
-- returns center ratio (without limits check) we would get with this panBy
|
|
|
|
local center_x_ratio = (x + self._offset_x + self.width/2) / self._bb_w
|
|
|
|
local center_y_ratio = (y + self._offset_y + self.height/2) / self._bb_h
|
|
|
|
return center_x_ratio, center_y_ratio
|
|
|
|
end
|
|
|
|
|
|
|
|
function ImageWidget:panBy(x, y)
|
|
|
|
-- update center ratio from new offset
|
|
|
|
self.center_x_ratio = (x + self._offset_x + self.width/2) / self._bb_w
|
|
|
|
self.center_y_ratio = (y + self._offset_y + self.height/2) / self._bb_h
|
|
|
|
-- correct new center ratio if out limits
|
|
|
|
if self.center_x_ratio < 0.5 - self._max_off_center_x_ratio then
|
|
|
|
self.center_x_ratio = 0.5 - self._max_off_center_x_ratio
|
|
|
|
elseif self.center_x_ratio > 0.5 + self._max_off_center_x_ratio then
|
|
|
|
self.center_x_ratio = 0.5 + self._max_off_center_x_ratio
|
|
|
|
end
|
|
|
|
if self.center_y_ratio < 0.5 - self._max_off_center_y_ratio then
|
|
|
|
self.center_y_ratio = 0.5 - self._max_off_center_y_ratio
|
|
|
|
elseif self.center_y_ratio > 0.5 + self._max_off_center_y_ratio then
|
|
|
|
self.center_y_ratio = 0.5 + self._max_off_center_y_ratio
|
|
|
|
end
|
|
|
|
-- new offsets that reflect this new center ratio
|
|
|
|
local new_offset_x = self.center_x_ratio * self._bb_w - self.width/2
|
|
|
|
local new_offset_y = self.center_y_ratio * self._bb_h - self.height/2
|
|
|
|
-- only trigger screen refresh it we actually pan
|
|
|
|
if new_offset_x ~= self._offset_x or new_offset_y ~= self._offset_y then
|
|
|
|
self._offset_x = new_offset_x
|
|
|
|
self._offset_y = new_offset_y
|
Enable HW dithering in a few key places (#4541)
* Enable HW dithering on supported devices (Clara HD, Forma; Oasis 2, PW4)
* FileManager and co. (where appropriate, i.e., when covers are shown)
* Book Status
* Reader, where appropriate:
* CRe: on pages whith image content (for over 7.5% of the screen area, should hopefully leave stuff like bullet points or small scene breaks alone).
* Other engines: on user-request (in the gear tab of the bottom menu), via the new "Dithering" knob (will only appear on supported devices).
* ScreenSaver
* ImageViewer
* Minimize repaints when flash_ui is enabled (by, almost everywhere, only repainting the flashing element, and not the toplevel window which hosts it).
(The first pass of this involved fixing a few Button instances whose show_parent was wrong, in particular, chevrons in the FM & TopMenu).
* Hunted down a few redundant repaints (unneeded setDirty("all") calls),
either by switching the widget to nil when only a refresh was needed, and not a repaint,
or by passing the appropritate widget to setDirty.
(Note to self: Enable *verbose* debugging to catch broken setDirty calls via its post guard).
There were also a few instances of 'em right behind a widget close.
* Don't repaint the underlying widget when initially showing TopMenu & ConfigDialog.
We unfortunately do need to do it when switching tabs, because of their variable heights.
* On Kobo, disabled the extra and completely useless full refresh before suspend/reboot/poweroff, as well as on resume. No more double refreshes!
* Fix another debug guard in Kobo sysfs_light
* Switch ImageWidget & ImageViewer mostly to "ui" updates, which will be better suited to image content pretty much everywhere, REAGL or not.
PS: (Almost :100: commits! :D)
2019-02-07 00:14:37 +00:00
|
|
|
self.dithered = true
|
2017-01-26 07:56:24 +00:00
|
|
|
UIManager:setDirty("all", function()
|
Enable HW dithering in a few key places (#4541)
* Enable HW dithering on supported devices (Clara HD, Forma; Oasis 2, PW4)
* FileManager and co. (where appropriate, i.e., when covers are shown)
* Book Status
* Reader, where appropriate:
* CRe: on pages whith image content (for over 7.5% of the screen area, should hopefully leave stuff like bullet points or small scene breaks alone).
* Other engines: on user-request (in the gear tab of the bottom menu), via the new "Dithering" knob (will only appear on supported devices).
* ScreenSaver
* ImageViewer
* Minimize repaints when flash_ui is enabled (by, almost everywhere, only repainting the flashing element, and not the toplevel window which hosts it).
(The first pass of this involved fixing a few Button instances whose show_parent was wrong, in particular, chevrons in the FM & TopMenu).
* Hunted down a few redundant repaints (unneeded setDirty("all") calls),
either by switching the widget to nil when only a refresh was needed, and not a repaint,
or by passing the appropritate widget to setDirty.
(Note to self: Enable *verbose* debugging to catch broken setDirty calls via its post guard).
There were also a few instances of 'em right behind a widget close.
* Don't repaint the underlying widget when initially showing TopMenu & ConfigDialog.
We unfortunately do need to do it when switching tabs, because of their variable heights.
* On Kobo, disabled the extra and completely useless full refresh before suspend/reboot/poweroff, as well as on resume. No more double refreshes!
* Fix another debug guard in Kobo sysfs_light
* Switch ImageWidget & ImageViewer mostly to "ui" updates, which will be better suited to image content pretty much everywhere, REAGL or not.
PS: (Almost :100: commits! :D)
2019-02-07 00:14:37 +00:00
|
|
|
return "ui", self.dimen, true
|
2017-01-26 07:56:24 +00:00
|
|
|
end)
|
|
|
|
end
|
|
|
|
-- return new center ratio, so caller can use them later to create a new
|
|
|
|
-- ImageWidget with a different scale_factor, while keeping center point
|
|
|
|
return self.center_x_ratio, self.center_y_ratio
|
2014-08-14 12:11:21 +00:00
|
|
|
end
|
|
|
|
|
2013-03-12 17:18:53 +00:00
|
|
|
function ImageWidget:paintTo(bb, x, y)
|
2014-08-22 09:22:41 +00:00
|
|
|
if self.hide then return end
|
2017-01-26 07:56:24 +00:00
|
|
|
-- self:_render is called in getSize method
|
2014-03-13 13:52:43 +00:00
|
|
|
local size = self:getSize()
|
|
|
|
self.dimen = Geom:new{
|
|
|
|
x = x, y = y,
|
|
|
|
w = size.w,
|
2014-08-14 10:08:52 +00:00
|
|
|
h = size.h
|
2014-03-13 13:52:43 +00:00
|
|
|
}
|
2017-01-26 07:56:24 +00:00
|
|
|
logger.dbg("blitFrom", x, y, self._offset_x, self._offset_y, size.w, size.h)
|
2020-12-19 11:18:30 +00:00
|
|
|
local do_alpha = false
|
2014-11-28 15:31:54 +00:00
|
|
|
if self.alpha == true then
|
2019-03-14 19:58:45 +00:00
|
|
|
-- Only actually try to alpha-blend if the image really has an alpha channel...
|
|
|
|
local bbtype = self._bb:getType()
|
|
|
|
if bbtype == Blitbuffer.TYPE_BB8A or bbtype == Blitbuffer.TYPE_BBRGB32 then
|
2020-12-19 11:18:30 +00:00
|
|
|
do_alpha = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if do_alpha then
|
2020-12-19 21:34:16 +00:00
|
|
|
--- @note: MuPDF feeds us premultiplied alpha (and we don't care w/ GifLib, as alpha is all or nothing),
|
|
|
|
--- while NanoSVG feeds us straight alpha.
|
|
|
|
--- SVG icons are currently flattened at caching time, so we'll only go through the straight alpha
|
|
|
|
--- codepath for non-icons SVGs.
|
2020-12-19 11:18:30 +00:00
|
|
|
if self._is_straight_alpha then
|
2020-12-19 21:34:16 +00:00
|
|
|
--- @note: Our icons are already dithered properly, either at encoding time, or at caching time.
|
|
|
|
if Screen.sw_dithering and not self.is_icon then
|
|
|
|
bb:ditheralphablitFrom(self._bb, x, y, self._offset_x, self._offset_y, size.w, size.h)
|
|
|
|
else
|
|
|
|
bb:alphablitFrom(self._bb, x, y, self._offset_x, self._offset_y, size.w, size.h)
|
|
|
|
end
|
2020-12-19 11:18:30 +00:00
|
|
|
else
|
2020-12-19 21:34:16 +00:00
|
|
|
if Screen.sw_dithering and not self.is_icon then
|
2019-04-18 21:26:53 +00:00
|
|
|
bb:ditherpmulalphablitFrom(self._bb, x, y, self._offset_x, self._offset_y, size.w, size.h)
|
|
|
|
else
|
|
|
|
bb:pmulalphablitFrom(self._bb, x, y, self._offset_x, self._offset_y, size.w, size.h)
|
|
|
|
end
|
2019-03-14 19:58:45 +00:00
|
|
|
end
|
2014-11-28 15:31:54 +00:00
|
|
|
else
|
2020-12-19 21:34:16 +00:00
|
|
|
if Screen.sw_dithering and not self.is_icon then
|
2019-04-18 21:26:53 +00:00
|
|
|
bb:ditherblitFrom(self._bb, x, y, self._offset_x, self._offset_y, size.w, size.h)
|
|
|
|
else
|
|
|
|
bb:blitFrom(self._bb, x, y, self._offset_x, self._offset_y, size.w, size.h)
|
|
|
|
end
|
2014-11-28 15:31:54 +00:00
|
|
|
end
|
2014-03-13 13:52:43 +00:00
|
|
|
if self.invert then
|
|
|
|
bb:invertRect(x, y, size.w, size.h)
|
|
|
|
end
|
2020-12-19 21:34:16 +00:00
|
|
|
--- @note: This is mainly geared at black icons/text on a *white* background,
|
|
|
|
--- otherwise the background color itself will shift.
|
|
|
|
--- i.e., this actually *lightens* the rectangle, but since it's aimed at black,
|
|
|
|
--- it makes it gray, dimmer; hence the name.
|
|
|
|
--- TL;DR: If we one day want that to work for icons on a non-white background,
|
|
|
|
--- a better solution would probably be to take the icon pixmap as an alpha-mask,
|
|
|
|
--- (which simply involves blending it onto a white background, then inverting the result),
|
|
|
|
--- and colorBlit it a dim gray onto the target bb.
|
|
|
|
--- This would require the *original* transparent icon, not the flattened one in the cache.
|
|
|
|
--- c.f., https://github.com/koreader/koreader/pull/6937#issuecomment-748372429 for a PoC
|
2014-03-13 13:52:43 +00:00
|
|
|
if self.dim then
|
|
|
|
bb:dimRect(x, y, size.w, size.h)
|
|
|
|
end
|
2020-12-19 21:34:16 +00:00
|
|
|
-- In night mode, invert all rendered images, so the original is
|
2019-03-29 23:04:38 +00:00
|
|
|
-- displayed when the whole screen is inverted by night mode.
|
2020-12-19 21:34:16 +00:00
|
|
|
-- Except for our *black & white* icons: we do *NOT* want to invert them again:
|
|
|
|
-- they should match the UI's text/backgound.
|
|
|
|
--- @note: As for *color* icons, we really *ought* to invert them here,
|
|
|
|
--- but we currently don't, as we don't really trickle down
|
|
|
|
--- a way to discriminate them from the B&W ones.
|
|
|
|
--- Currently, this is *only* the KOReader icon in Help, AFAIK.
|
2020-12-19 11:18:30 +00:00
|
|
|
if Screen.night_mode and not self.is_icon then
|
2019-03-29 23:04:38 +00:00
|
|
|
bb:invertRect(x, y, size.w, size.h)
|
|
|
|
end
|
2013-03-12 17:18:53 +00:00
|
|
|
end
|
|
|
|
|
2017-01-15 20:47:22 +00:00
|
|
|
-- This will normally be called by our WidgetContainer:free()
|
|
|
|
-- But it SHOULD explicitely be called if we are getting replaced
|
|
|
|
-- (ie: in some other widget's update()), to not leak memory with
|
|
|
|
-- BlitBuffer zombies
|
2014-08-27 03:07:25 +00:00
|
|
|
function ImageWidget:free()
|
Tame some ButtonTable users into re-using Buttontable instances if possible (#7166)
* QuickDictLookup, ImageViewer, NumberPicker: Smarter `update` that will re-use most of the widget's layout instead of re-instantiating all the things.
* SpinWidget/DoubleSpinWidget: The NumberPicker change above renders a hack to preserve alpha on these widgets almost unnecessary. Also fixed said hack to also apply to the center, value button.
* Button: Don't re-instantiate the frame in setText/setIcon when unnecessary (e.g., no change at all, or no layout change).
* Button: Add a refresh method that repaints and refreshes a *specific* Button (provided it's been painted once) all on its lonesome.
* ConfigDialog: Free everything that's going to be re-instatiated on update
* A few more post #7118 fixes:
* SkimTo: Always flag the chapter nav buttons as vsync
* Button: Fix the highlight on rounded buttons when vsync is enabled (e.g., it's now entirely visible, instead of showing a weird inverted corner glitch).
* Some more heuristic tweaks in Menu/TouchMenu/Button/IconButton
* ButtonTable: fix the annoying rounding issue I'd noticed in #7054 ;).
* Enable dithering in TextBoxWidget (e.g., in the Wikipedia full view). This involved moving the HW dithering align fixup to base, where it always ought to have been ;).
* Switch a few widgets that were using "partial" on close to "ui", or, more rarely, "flashui". The intent being to limit "partial" purely to the Reader, because it has a latency cost when mixed with other refreshes, which happens often enough in UI ;).
* Minor documentation tweaks around UIManager's `setDirty` to reflect that change.
* ReaderFooter: Force a footer repaint on resume if it is visible (otherwise, just update it).
* ReaderBookmark: In the same vein, don't repaint an invisible footer on bookmark count changes.
2021-01-28 23:20:15 +00:00
|
|
|
--print("ImageWidget:free on", self, "for BB?", self._bb, self._bb_disposable)
|
2017-01-15 20:47:22 +00:00
|
|
|
if self._bb and self._bb_disposable and self._bb.free then
|
|
|
|
self._bb:free()
|
|
|
|
self._bb = nil
|
2014-08-27 03:07:25 +00:00
|
|
|
end
|
2017-01-26 07:56:24 +00:00
|
|
|
-- reset self.scale_factor to its initial value, in case
|
|
|
|
-- self._render() is called again (happens with iconbutton,
|
|
|
|
-- avoids x2 x2 x2 if high dpi and icon scaled x8 after 3 calls)
|
|
|
|
self.scale_factor = self._initial_scale_factor
|
2014-08-27 03:07:25 +00:00
|
|
|
end
|
|
|
|
|
2017-01-15 20:47:22 +00:00
|
|
|
function ImageWidget:onCloseWidget()
|
|
|
|
-- free when UIManager:close() was called
|
|
|
|
self:free()
|
|
|
|
end
|
|
|
|
|
2013-10-18 20:38:07 +00:00
|
|
|
return ImageWidget
|