RenderImage: add renderSVGImageFile() (#6950)

ImageWidget: allow picking SVG versions of provided
PNG icons if available.
reviewable/pr6958/r1
poire-z 4 years ago committed by GitHub
parent 0a6ef6e351
commit c20ad8f5e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -2,12 +2,15 @@
Image rendering module.
]]
local Blitbuffer = require("ffi/blitbuffer")
local Math = require("optmath")
local ffi = require("ffi")
local logger = require("logger")
-- Will be loaded when needed
local Mupdf = nil
local Pic = nil
local NnSVG = nil
local RenderImage = {}
@ -184,4 +187,110 @@ function RenderImage:scaleBlitBuffer(bb, width, height, free_orig_bb)
return scaled_bb
end
--- Renders SVG image file as a BlitBuffer with the best renderer
--
-- @string filename image file path
-- @int width requested width
-- @int height requested height
-- @number zoom requested zoom
-- @treturn BlitBuffer
function RenderImage:renderSVGImageFile(filename, width, height, zoom)
if self.RENDER_SVG_WITH_NANOSVG then
return self:renderSVGImageFileWithNanoSVG(filename, width, height, zoom)
else
return self:renderSVGImageFileWithMupdf(filename, width, height, zoom)
end
end
-- For now (with our old MuPDF 1.13), NanoSVG is the best renderer
-- Note that both renderers currently enforce keeping the image's
-- original aspect ratio.
RenderImage.RENDER_SVG_WITH_NANOSVG = true
function RenderImage:renderSVGImageFileWithNanoSVG(filename, width, height, zoom)
if not NnSVG then
NnSVG = require("libs/libkoreader-nnsvg")
end
local svg_image = NnSVG.new(filename)
local native_w, native_h = svg_image:getSize()
if not zoom then
if width and height then
-- Original aspect ratio will be kept, we might have
-- to center the SVG inside the target width/height
zoom = math.min(width/native_w, height/native_h)
elseif width then
zoom = width/native_w
elseif height then
zoom = height/native_h
else
zoom = 1
end
end
-- (Be sure we use integers; using floats can cause glitches)
local inner_w = math.ceil(zoom * native_w)
local inner_h = math.ceil(zoom * native_h)
local offset_x = 0
local offset_y = 0
if not width then
width = inner_w
elseif inner_w < width then
offset_x = Math.round((width - inner_w) / 2)
end
if not height then
height = inner_h
elseif inner_h < height then
offset_y = Math.round((height - inner_h) / 2)
end
logger.dbg("renderSVG", filename, zoom, native_w, native_h, ">", width, height, offset_x, offset_y)
local bb = Blitbuffer.new(width, height, Blitbuffer.TYPE_BBRGB32)
svg_image:drawTo(bb, zoom, offset_x, offset_y)
svg_image:free()
return bb
end
function RenderImage:renderSVGImageFileWithMupdf(filename, width, height, zoom)
local ok, document = pcall(Mupdf.openDocument, filename)
if not ok then
return
end
-- document:layoutDocument(width, height, 20) -- does not change anything
if document:getPages() <= 0 then
return
end
local page = document:openPage(1)
local DrawContext = require("ffi/drawcontext")
local dc = DrawContext.new()
local native_w, native_h = page:getSize(dc)
if not zoom then
if width and height then
zoom = math.min(width/native_w, height/native_h)
elseif width then
zoom = width/native_w
elseif height then
zoom = height/native_h
else
zoom = 1
end
end
if not width or not height then
width = zoom * native_w
height = zoom * native_h
end
width = math.ceil(width)
height = math.ceil(height)
logger.dbg("renderSVG", filename, zoom, native_w, native_h, ">", width, height)
dc:setZoom(zoom)
-- local bb = page:draw_new(dc, width, height, 0, 0)
-- MuPDF or our FFI may fail on some icons (appbar.page.fit),
-- avoid a crash and return a blank and black image
local rendered, bb = pcall(page.draw_new, page, dc, width, height, 0, 0)
if not rendered then
logger.warn("MuPDF renderSVG error:", bb)
bb = Blitbuffer.new(width, height, Blitbuffer.TYPE_BBRGB32)
end
page:close()
document:close()
return bb
end
return RenderImage

@ -127,10 +127,32 @@ function ImageWidget:_loadimage()
self._bb_disposable = self.image_disposable
end
local ICONS_ALT_SVG_DIR = false
-- Uncomment to use SVG icons from one of these directories
-- ICONS_ALT_SVG_DIR = "resources/icons/src/"
-- ICONS_ALT_SVG_DIR = "resources/icons/svg/"
function ImageWidget:_loadfile()
if ICONS_ALT_SVG_DIR then
-- Pick the SVG version if one exists when a png icon file path is provided
local dir, name = self.file:match("^(resources/icons/)([^/]*).png$")
if dir and name then
local svg_file = ICONS_ALT_SVG_DIR .. name .. ".svg"
if lfs.attributes(svg_file, "mode") ~= "file" then
svg_file = svg_file:gsub(".large", "") -- Try with this removed
if lfs.attributes(svg_file, "mode") ~= "file" then
svg_file = nil -- no alt svg available
end
end
if svg_file then
logger.dbg("using alt SVG", svg_file)
self.file = svg_file
end
end
end
local itype = string.lower(string.match(self.file, ".+%.([^.]+)") or "")
if itype == "png" or itype == "jpg" or itype == "jpeg"
or itype == "tiff" or itype == "tif" or itype == "gif" then
if itype == "svg" or itype == "png" or itype == "jpg" or itype == "jpeg"
or itype == "gif" or itype == "tiff" or itype == "tif" then
-- In our use cases for files (icons), we either provide width and height,
-- or just scale_for_dpi, and scale_factor should stay nil.
-- Other combinations will result in double scaling, and unexpected results.
@ -157,10 +179,24 @@ function ImageWidget:_loadfile()
self._bb = cache.bb
self._bb_disposable = false -- don't touch or free a cached _bb
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))
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
-- local start_ts = require("ffi/util").getTimestamp() -- Uncomment for timing things
self._bb = RenderImage:renderSVGImageFile(self.file, width, height, zoom)
-- logger.info(string.format(" SVG rendering %.6f s", require("ffi/util").getDuration(start_ts)), self.file, zoom or "", width, height)
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
end
if not self.file_do_cache then
self._bb_disposable = true -- we made it, we can modify and free it

Loading…
Cancel
Save