Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Render SVGs with librsvg #124

Merged
merged 2 commits into from
Dec 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,19 @@ 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

- name: Build timg
run: |
mkdir build
cd build
cmake .. -DWITH_VIDEO_DECODING=On -DWITH_VIDEO_DEVICE=On -DWITH_OPENSLIDE_SUPPORT=On
make -k

- name: Print timg Version string
run: |
build/src/timg --version
16 changes: 14 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,18 @@ 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: Print timg Version string
run: |
echo "------------------- Limited dependency version string --------"
build-limitdep/src/timg --version
echo

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 Expand Up @@ -131,6 +141,7 @@ jobs:
sudo apt install cmake git g++ clang pkg-config
sudo apt install libswscale-dev libavutil-dev libdeflate-dev
sudo apt install libturbojpeg-dev libexif-dev
sudo apt install librsvg2-dev libcairo-dev
sudo apt install libsixel-dev
sudo apt install pandoc

Expand All @@ -144,6 +155,7 @@ jobs:
# also check apt to include in .github/conf/AppImageBuilder.yml
./.github/bin/prepare-app-image.sh \
-DWITH_GRAPHICSMAGICK=Off \
-DWITH_RSVG=On \
-DWITH_VIDEO_DECODING=Off \
-DWITH_VIDEO_DEVICE=Off \
-DWITH_OPENSLIDE_SUPPORT=Off \
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
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -480,10 +480,11 @@ brew install timg
#### Use AppImage

The [timg release page](https://github.com/hzeller/timg/releases/latest) also
has a minimal binary in the [AppImage package format][AppImage].
To keep the size small, it does not include video decoding or some more
fancy image formats. It is good for many contexts, but for a full-featured
binary, use one from your distribution or build from source.
has a _minimal_ binary in the [AppImage package format][AppImage].
To keep the size small, it does _not_ include video decoding or some more
fancy image formats. It is good for many contexts or if you want to try out
`timg`, but for a full-featured binary, use one from your distribution or
build from source.

### Build and Install from source

Expand Down Expand Up @@ -557,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
106 changes: 106 additions & 0 deletions src/svg-image-source.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*-
// (c) 2023 Henner Zeller <h.zeller@acm.org>
//
// 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: for non-1:1 aspect ratio of the output (e.g. pixelation=quarter),
// the resulting aspect ratio is squished, as we have to do the
// distortion ourself.
// 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 <h.zeller@acm.org>
//
// 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