mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
Implement proper alpha-blending of SVG icons (#7011)
* Implement proper alpha-blending of SVG icons Also, instead of doing that every time, cache a pre-composited version that matches the screen's BB type. This is faster, and also has the advantage of making icon highlights behave. Jot down a few notes about corner-cases or future improvements, e.g., dimming icons on non-white backgrounds, and nightmode with color icons.
This commit is contained in:
@@ -35,7 +35,7 @@ local IconWidget = ImageWidget:extend{
|
||||
-- be overriden by callers.
|
||||
width = Screen:scaleBySize(DGENERIC_ICON_SIZE), -- our icons are square
|
||||
height = Screen:scaleBySize(DGENERIC_ICON_SIZE),
|
||||
alpha = true, -- our icons have a transparent background
|
||||
alpha = false, --- @note: our icons have a transparent background, but we flatten them at caching time, and this flag is only checked at blitting time.
|
||||
is_icon = true, -- avoid dithering in ImageWidget:paintTo()
|
||||
}
|
||||
|
||||
|
||||
@@ -178,6 +178,45 @@ function ImageWidget:_loadfile()
|
||||
self._bb = RenderImage:scaleBlitBuffer(self._bb, math.floor(bb_w * DPI_SCALE), math.floor(bb_h * DPI_SCALE))
|
||||
end
|
||||
end
|
||||
|
||||
-- Now, if that was *also* one of our icons, and it has an alpha channel,
|
||||
-- compose it against a background-colored BB now, and cache *that*.
|
||||
-- This helps us avoid repeating alpha-blending steps down the line,
|
||||
-- and also ensures icon highlights/unhilights behave sensibly.
|
||||
if self.is_icon then
|
||||
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
|
||||
|
||||
if not self.file_do_cache then
|
||||
self._bb_disposable = true -- we made it, we can modify and free it
|
||||
else
|
||||
@@ -384,20 +423,26 @@ function ImageWidget:paintTo(bb, x, y)
|
||||
end
|
||||
end
|
||||
if do_alpha then
|
||||
-- NOTE: MuPDF feeds us premultiplied alpha (and we don't care w/ GifLib, as alpha is all or nothing),
|
||||
-- but NanoSVG feeds us straight alpha
|
||||
--- @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.
|
||||
if self._is_straight_alpha then
|
||||
--- @todo if Screen.sw_dithering then use bb:ditheralphablitFrom() when it's available
|
||||
bb:alphablitFrom(self._bb, x, y, self._offset_x, self._offset_y, size.w, size.h)
|
||||
--- @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
|
||||
else
|
||||
if Screen.sw_dithering then
|
||||
if Screen.sw_dithering and not self.is_icon then
|
||||
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
|
||||
end
|
||||
else
|
||||
if Screen.sw_dithering then
|
||||
if Screen.sw_dithering and not self.is_icon then
|
||||
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)
|
||||
@@ -406,13 +451,27 @@ function ImageWidget:paintTo(bb, x, y)
|
||||
if self.invert then
|
||||
bb:invertRect(x, y, size.w, size.h)
|
||||
end
|
||||
--- @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
|
||||
if self.dim then
|
||||
bb:dimRect(x, y, size.w, size.h)
|
||||
end
|
||||
-- If in night mode, invert all rendered images, so the original is
|
||||
-- In night mode, invert all rendered images, so the original is
|
||||
-- displayed when the whole screen is inverted by night mode.
|
||||
-- Except for our black & white icon files, that we do want inverted
|
||||
-- in night mode.
|
||||
-- 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.
|
||||
if Screen.night_mode and not self.is_icon then
|
||||
bb:invertRect(x, y, size.w, size.h)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user