Skip to content

Commit

Permalink
Render SVGs with librsvg
Browse files Browse the repository at this point in the history
This uses the librsvg library to render SVG images, which results
in much higher quality output than we get from graphicsmagick.

Issues #122
  • Loading branch information
hzeller committed Dec 16, 2023
1 parent 22a2eb8 commit b066739
Show file tree
Hide file tree
Showing 11 changed files with 255 additions and 52 deletions.
1 change: 1 addition & 0 deletions .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
brew install libdeflate libsixel
brew install ffmpeg jpeg-turbo libexif libpng
brew install openslide
brew install librsvg cairo
brew install pandoc
- name: Get the Source
uses: actions/checkout@v3
Expand Down
13 changes: 11 additions & 2 deletions .github/workflows/ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
run: |
mkdir build-limitdep
cd build-limitdep
cmake .. -DWITH_VIDEO_DECODING=Off -DWITH_VIDEO_DEVICE=Off -DWITH_OPENSLIDE_SUPPORT=Off -DWITH_GRAPHICSMAGICK=Off -DWITH_TURBOJPEG=Off -DWITH_LIBSIXEL=Off
cmake .. -DWITH_VIDEO_DECODING=Off -DWITH_VIDEO_DEVICE=Off -DWITH_OPENSLIDE_SUPPORT=Off -DWITH_GRAPHICSMAGICK=Off -DWITH_TURBOJPEG=Off -DWITH_RSVG=Off -DWITH_LIBSIXEL=Off
make -k
- name: Install Full Dependencies
Expand All @@ -45,6 +45,7 @@ jobs:
sudo apt install libgraphicsmagick++-dev
sudo apt install libturbojpeg-dev libexif-dev
sudo apt install libsixel-dev
sudo apt install librsvg2-dev libcairo-dev
sudo apt install libavcodec-dev libavformat-dev libavdevice-dev
sudo apt install libopenslide-dev
sudo apt install pandoc
Expand All @@ -53,9 +54,17 @@ jobs:
run: |
mkdir build
cd build
cmake .. -DWITH_VIDEO_DECODING=On -DWITH_VIDEO_DEVICE=On -DWITH_OPENSLIDE_SUPPORT=On -DWITH_STB_IMAGE=On -DWITH_LIBSIXEL=On
cmake .. -DWITH_VIDEO_DECODING=On -DWITH_VIDEO_DEVICE=On -DWITH_OPENSLIDE_SUPPORT=On -DWITH_STB_IMAGE=On -DWITH_RSVG=On -DWITH_LIBSIXEL=On
make -k
- name: Version string with low dependencies
run: |
echo "Limited dependency version string"
build-limitdep/src/timg --version
echo "All dependencies version string"
build/src/timg --version
CodeFormatting:
if: false # currently, there is no clang-format-13 in ubuntu latest
runs-on: ubuntu-latest
Expand Down
16 changes: 12 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@ project(timg VERSION 1.5.3 LANGUAGES CXX)

option(WITH_VIDEO_DECODING "Enables video decoding feature" ON)
option(WITH_VIDEO_DEVICE "Enables reading videos from devices e.g. v4l2 (requires WITH_VIDEO_DECODING)" ON)
option(WITH_OPENSLIDE_SUPPORT "Enables support to scientific OpenSlide formats" OFF)

# Options that should be typically on, but could be disabled for special
# applications where less dependencies are required
option(WITH_GRAPHICSMAGICK "Enable general image loading with Graphicsmagick. You typically want this." ON)
option(WITH_TURBOJPEG "Optimized JPEG loading. You typically want this." ON)
option(WITH_LIBSIXEL "Provide sixel output which is supported by some older terminals such as xterm" ON)

option(WITH_RSVG "Use librsvg to open SVG images." ON)
option(WITH_STB_IMAGE "Use STB image, a self-contained albeit limited image loading and lower quality. Use if WITH_GRAPHICSMAGICK is not possible and want to limit dependencies. Default on to be used as fallback." ON)

option(WITH_QOI_IMAGE "QOI image format" ON)

# Compile-time option for specialized
option(WITH_OPENSLIDE_SUPPORT "Enables support to scientific OpenSlide formats" OFF)

# Output formats
option(WITH_LIBSIXEL "Provide sixel output which is supported by some older terminals such as xterm" ON)

# Note: The version string can be ammended with -DDISTRIBUTION_VERSION, see src/timg-version.h.in
option(TIMG_VERSION_FROM_GIT "Get the program version from the git repository" ON)

Expand Down Expand Up @@ -52,6 +55,11 @@ if(WITH_GRAPHICSMAGICK)
pkg_check_modules(GRAPHICSMAGICKXX IMPORTED_TARGET REQUIRED GraphicsMagick++)
endif()

if(WITH_RSVG)
pkg_check_modules(RSVG REQUIRED IMPORTED_TARGET librsvg-2.0)
pkg_check_modules(CAIRO REQUIRED IMPORTED_TARGET cairo)
endif()

if(WITH_OPENSLIDE_SUPPORT)
pkg_check_modules(OPENSLIDE IMPORTED_TARGET REQUIRED openslide)
pkg_check_modules(AVUTIL REQUIRED IMPORTED_TARGET libavutil)
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,9 @@ compile-time choices:
typically want this **ON** (default).
* **`WITH_TURBOJPEG`** If enabled, uses this for faster jpeg file loading.
You typically want this **ON** (default).
* **`WITH_RSVG`** High-quality SVG renderer. Needs librsvg and cairo.
If not compiled-in, will fallback to GraphicsMagick, but that typically
results in lower quality renderings. Typically want this **ON** (default).
* **`WITH_OPENSLIDE_SUPPORT`** Openslide is an image format used in scientific
applications. Rarely used, so default off, switch ON if needed.
* **`WITH_QOI_IMAGE`** Allow decoding of Quite Ok Image format [QOI]. Small
Expand Down
7 changes: 7 additions & 0 deletions shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ pkgs.mkShell {
ffmpeg
libexif
libsixel
librsvg cairo

# Don't include qoi and stb by default to see if the cmake
# fallback to third_party/ works.
#qoi
#stb

openslide
pandoc
clang-tools_13 # clang-format
Expand Down
6 changes: 6 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ if(WITH_GRAPHICSMAGICK)
target_link_libraries(timg PkgConfig::GRAPHICSMAGICKXX)
endif()

if(WITH_RSVG)
target_sources(timg PUBLIC svg-image-source.h svg-image-source.cc)
target_compile_definitions(timg PUBLIC WITH_TIMG_RSVG)
target_link_libraries(timg PkgConfig::RSVG PkgConfig::CAIRO)
endif()

if(WITH_TURBOJPEG)
target_sources(timg PUBLIC jpeg-source.h jpeg-source.cc)
target_compile_definitions(timg PUBLIC WITH_TIMG_JPEG)
Expand Down
2 changes: 1 addition & 1 deletion src/framebuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
namespace timg {
struct rgba_t {
uint8_t r, g, b; // Color components, gamma corrected (non-linear)
uint8_t a; // Alpha channel. Linear.
uint8_t a; // Alpha channel. Linear. [transparent..opaque]=0..255

inline bool operator==(const rgba_t &that) const {
// Using memcmp() slower, so force uint-compare with type-punning.
Expand Down
8 changes: 8 additions & 0 deletions src/image-source.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "openslide-source.h"
#include "qoi-image-source.h"
#include "stb-image-source.h"
#include "svg-image-source.h"
#include "video-display.h"

namespace timg {
Expand Down Expand Up @@ -176,6 +177,13 @@ ImageSource *ImageSource::Create(const std::string &filename,
}
#endif

#ifdef WITH_TIMG_RSVG
result.reset(new SVGImageSource(filename));
if (result->LoadAndScale(options, frame_offset, frame_count)) {
return result.release();
}
#endif

#ifdef WITH_TIMG_GRPAPHICSMAGICK
result.reset(new ImageLoader(filename));
if (result->LoadAndScale(options, frame_offset, frame_count)) {
Expand Down
103 changes: 103 additions & 0 deletions src/svg-image-source.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*-
// (c) 2023 Henner Zeller <[email protected]>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://gnu.org/licenses/gpl-2.0.txt>

#include "svg-image-source.h"

#include <cairo.h>
#include <librsvg/rsvg.h>
#include <stdlib.h>

#include "framebuffer.h"

namespace timg {

std::string SVGImageSource::FormatTitle(
const std::string &format_string) const {
return FormatFromParameters(format_string, filename_, (int)orig_width_,
(int)orig_height_, "svg");
}

bool SVGImageSource::LoadAndScale(const DisplayOptions &opts, int, int) {
options_ = opts;
RsvgHandle *svg = rsvg_handle_new_from_file(filename_.c_str(), nullptr);
if (!svg) return false;

RsvgRectangle viewbox;
gboolean out_has_width, out_has_height, out_has_viewbox;
RsvgLength svg_width, svg_height;
rsvg_handle_get_intrinsic_dimensions(svg, &out_has_width, &svg_width,
&out_has_height, &svg_height,
&out_has_viewbox, &viewbox);
if (out_has_viewbox) {
orig_width_ = viewbox.width;
orig_height_ = viewbox.height;
}
else if (out_has_width && out_has_height) {
// We ignore the unit, but this will still result in proper aspect ratio
orig_width_ = svg_width.length;
orig_height_ = svg_height.length;
}

int target_width;
int target_height;
CalcScaleToFitDisplay(orig_width_, orig_height_, opts, false, &target_width,
&target_height);

const auto kCairoFormat = CAIRO_FORMAT_ARGB32;
int stride = cairo_format_stride_for_width(kCairoFormat, target_width);
image_.reset(new timg::Framebuffer(stride / 4, target_height));

cairo_surface_t *surface = cairo_image_surface_create_for_data(
(uint8_t *)image_->begin(), kCairoFormat, target_width, target_height,
stride);
cairo_t *cr = cairo_create(surface);

RsvgRectangle viewport = {
.x = 0.0,
.y = 0.0,
.width = (double)target_width,
.height = (double)target_height,
};

bool success = rsvg_handle_render_document(svg, cr, &viewport, nullptr);
cairo_destroy(cr);
cairo_surface_destroy(surface);
g_object_unref(svg);

// TODO: if (int(stride / sizeof(rgba_t)) != target_width) : copy over

// If requested, merge background with pattern.
image_->AlphaComposeBackground(
options_.bgcolor_getter, options_.bg_pattern_color,
options_.pattern_size * options_.cell_x_px,
options_.pattern_size * options_.cell_y_px / 2);

return success;
}

int SVGImageSource::IndentationIfCentered(
const timg::Framebuffer &image) const {
return options_.center_horizontally ? (options_.width - image.width()) / 2
: 0;
}

void SVGImageSource::SendFrames(const Duration &duration, int loops,
const volatile sig_atomic_t &interrupt_received,
const Renderer::WriteFramebufferFun &sink) {
sink(IndentationIfCentered(*image_), 0, *image_, SeqType::FrameImmediate,
{});
}

} // namespace timg
50 changes: 50 additions & 0 deletions src/svg-image-source.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*-
// (c) 2023 Henner Zeller <[email protected]>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://gnu.org/licenses/gpl-2.0.txt>

#ifndef SVG_SOURCE_H_
#define SVG_SOURCE_H_

#include <memory>

#include "display-options.h"
#include "image-source.h"
#include "terminal-canvas.h"

namespace timg {
class SVGImageSource final : public ImageSource {
public:
explicit SVGImageSource(const std::string &filename)
: ImageSource(filename) {}

bool LoadAndScale(const DisplayOptions &options, int frame_offset,
int frame_count) final;

void SendFrames(const Duration &duration, int loops,
const volatile sig_atomic_t &interrupt_received,
const Renderer::WriteFramebufferFun &sink) final;

std::string FormatTitle(const std::string &format_string) const final;

private:
int IndentationIfCentered(const timg::Framebuffer &image) const;

DisplayOptions options_;
double orig_width_, orig_height_;
std::unique_ptr<timg::Framebuffer> image_;
};

} // namespace timg

#endif // QOI_SOURCE_H_
Loading

0 comments on commit b066739

Please sign in to comment.