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

Add WebP support #233

Merged
merged 4 commits into from
Jun 15, 2021
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: 5 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ add_executable( "${BIN_TARGET}" ${source} )
set( CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${CMAKE_SOURCE_DIR}/modules" )
find_package( PNG REQUIRED )
find_package( JPEG REQUIRED )
find_package( WebP REQUIRED )
find_package( XRandr REQUIRED )
find_package( XRender REQUIRED )
find_package( XFixes REQUIRED )
Expand All @@ -54,7 +55,8 @@ include_directories( ${XRANDR_INCLUDE_DIR}
${JPEG_INCLUDE_DIR}
${XRANDR_INCLUDE_DIR}
${XRENDER_INCLUDE_DIR}
${PNG_INCLUDE_DIRS} )
${PNG_INCLUDE_DIRS}
${WEBP_INCLUDE_DIR} )

# Libraries
target_link_libraries( ${BIN_TARGET}
Expand All @@ -66,7 +68,8 @@ target_link_libraries( ${BIN_TARGET}
${XRANDR_LIBRARY}
${JPEG_LIBRARIES}
${XRENDER_LIBRARY}
${SLOP_LIBRARIES} )
${SLOP_LIBRARIES}
${WEBP_LIBRARY} )

if( ${CMAKE_VERSION} VERSION_LESS 3.7 )
message( WARNING "CMake version is below 3.7, CMake version >= 3.7 is required for unicode support." )
Expand Down
6 changes: 3 additions & 3 deletions maim.1
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ maim \- make image
.SH SYNOPSIS
maim [OPTIONS] [FILEPATH]
.SH DESCRIPTION
maim (make image) is an utility that takes a screenshot of your desktop, and encodes a png, jpg, or bmp image of it. By default it outputs the encoded image data directly to standard output.
maim (make image) is an utility that takes a screenshot of your desktop, and encodes a png, jpg, bmp or webp image of it. By default it outputs the encoded image data directly to standard output.
.SH OPTIONS
.TP
.BR \-h ", " \-\-help
Expand All @@ -19,7 +19,7 @@ Print version and exit.
Sets the xdisplay to use.
.TP
.BR \-f ", " \-\-format=\fISTRING\fR
Sets the desired output format, by default maim will attempt to determine the desired output format automatically from the output file. If that fails it defaults to a lossless png format. Currently only supports `png`, `jpg`, and `bmp`.
Sets the desired output format, by default maim will attempt to determine the desired output format automatically from the output file. If that fails it defaults to a lossless png format. Currently only supports `png`, `jpg`, `bmp`, and `webp`.
.TP
.BR \-i ", " \-\-window=\fIWINDOW\fR
By default, maim captures the root window. This parameter overrides this and sets the desired window to capture. Allows for an integer, hex, or `root` for input.
Expand All @@ -40,7 +40,7 @@ Sets the time in seconds to wait before taking a screenshot. Prints a simple mes
By default maim super-imposes the cursor onto the image, you can disable that behavior with this flag.
.TP
.BR \-m ", " \-\-quality
An integer from 1 to 10 that determines the compression quality. 1 is the highest (and lossiest) compression available for the provided format. For example a setting of `1` with png (a lossless format) would increase filesize and decrease decoding time. While a setting of `1` on a jpeg would create a pixel mush. No effect on bmp images.
An integer from 1 to 10 that determines the compression quality. For lossy formats (jpg and webp), lower settings will produce smaller files with lower quality, while higher settings will increase quality at the cost of higher file size. A quality of 10 is lossless for webp. For png, lower settings will compress faster and produce larger files, while higher settings will compress slower, but produce smaller files. No effect on bmp images.
.TP
.BR \-s ", " \-\-select
Enables an interactive selection mode where you may select the desired region or window before a screenshot is captured. Uses the settings below to determine the visuals and settings of slop.
Expand Down
25 changes: 25 additions & 0 deletions modules/FindWebP.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# - Find WebP
# Find the WebP libraries
#
# This module defines the following variables:
# WEBP_FOUND - 1 if WEBP_INCLUDE_DIR & WEBP_LIBRARY are found, 0 otherwise
# WEBP_INCLUDE_DIR - where to find webp/encode.h, etc.
# WEBP_LIBRARY - the libwebp library

find_path( WEBP_INCLUDE_DIR
NAMES webp/encode.h
PATH_SUFFIXES /usr/include /include
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably also a good idea to search for /usr/local/include? I am not sure though, just thinking.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm new to cmake, so I used the same paths as in the other Find*.cmake files. If we used /usr/local/ here, I feel like it'd also have to be changed for the other ones, which I think is a topic for another PR.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a cmake veteran either :D
If /local/ paths are not used in other Find*.cmake files, we should really not be bothered.

DOC "The libwebp include directory" )

find_library( WEBP_LIBRARY
NAMES libwebp.so
PATHS /usr/lib /lib
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And same deal here with /usr/local/lib?

DOC "The libwebp library" )

if( WEBP_INCLUDE_DIR AND WEBP_LIBRARY )
set( WEBP_FOUND 1 )
else()
set( WEBP_FOUND 0 )
endif()

mark_as_advanced( WEBP_INCLUDE_DIR WEBP_LIBRARY )
26 changes: 26 additions & 0 deletions src/image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,32 @@ void ARGBImage::writeBMP( std::ostream& streamout ) {
delete[] imageData;
}

void ARGBImage::writeWEBP( std::ostream& streamout, int quality ) {
// assume 4 channels
if (channels != 4) {
throw new std::runtime_error("WebP tried to save image with more than 4 channels");
}

size_t size;
uint8_t* out;
if (quality == 10) {
// encode lossless at highest quality
size = WebPEncodeLosslessRGBA(data, width, height, width * 4, &out);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, there is more advanced interface for lossless encoding:

struct WebPConfig {
  int lossless;           // Lossless encoding (0=lossy(default), 1=lossless).
  float quality;          // between 0 and 100. For lossy, 0 gives the smallest
                          // size and 100 the largest. For lossless, this
                          // parameter is the amount of effort put into the
                          // compression: 0 is the fastest but gives larger
                          // files compared to the slowest, but best, 100.
  int method;             // quality/speed trade-off (0=fast, 6=slower-better)

It allows us to specify encoding speed for webp in lossless mode. I am not sure if we should use this or just stick to default prefubs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw this as well, but I think we have no good reason to use other settings, unless the user can change them; but that would require more switches, which I think would introduce unnecessary complexity, which is also why I think re-using --quality for deciding between lossy/lossless encoding is good, compared to adding something like --lossless-webp.

}
else {
// otherwise, encode lossy
size = WebPEncodeRGBA(data, width, height, width * 4, quality * 10.0f, &out);
}

if (size == 0) {
throw new std::runtime_error("Failed to encode webp image");
}
else {
streamout.write((const char*)out, size);
WebPFree(out);
}
}

bool ARGBImage::intersect( XRRCrtcInfo* a, glm::vec4 b ) {
if (a->x < b.x + b.z &&
a->x + a->width > b.x &&
Expand Down
2 changes: 2 additions & 0 deletions src/image.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <iostream>
#include <png.h>
#include <jpeglib.h>
#include <webp/encode.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdexcept>
Expand Down Expand Up @@ -91,6 +92,7 @@ class ARGBImage {
void writePNG( std::ostream& streamout, int quality );
void writeJPEG( std::ostream& streamout, int quality );
void writeBMP( std::ostream& streamout );
void writeWEBP( std::ostream& streamout, int quality );
};

#endif
88 changes: 42 additions & 46 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ MaimOptions* getMaimOptions( cxxopts::Options& options, X11* x11 ) {
foo->formatGiven = options.count("format") > 0;
if ( foo->formatGiven ) {
foo->format = options["format"].as<std::string>();
if ( foo->format != "png" && foo->format != "jpg" && foo->format != "jpeg" && foo->format != "bmp") {
if ( foo->format != "png" && foo->format != "jpg" && foo->format != "jpeg" && foo->format != "bmp" && foo->format != "webp" ) {
throw new std::invalid_argument("Unknown format type: `" + foo->format + "`, only `png`, `jpg`, or `bmp` is allowed." );
}
}
Expand Down Expand Up @@ -272,8 +272,8 @@ SYNOPSIS

DESCRIPTION
maim (make image) is an utility that takes a screenshot of your desktop,
and encodes a png or jpg image of it. By default it outputs the encoded
image data directly to standard output.
and encodes a png, jpg, bmp or webp image of it. By default it outputs
the encoded image data directly to standard output.

OPTIONS
-h, --help
Expand All @@ -289,7 +289,7 @@ OPTIONS
Sets the desired output format, by default maim will attempt to
determine the desired output format automatically from the output
file. If that fails it defaults to a lossless png format. Cur‐
rently only supports `png` or `jpg`.
rently supports `png`, `jpg`, `bmp` and `webp`.

-i, --window=INT
Sets the desired window to capture, defaults to the root window.
Expand All @@ -310,11 +310,14 @@ OPTIONS
disable that behavior with this flag.

-m, --quality
An integer from 1 to 10 that determines the compression quality. 1
is the highest (and lossiest) compression available for the pro‐
vided format. For example a setting of `1` with png (a lossless
format) would increase filesize and speed up encoding dramatical-
ly. While a setting of `1` on a jpeg would create a pixel mush.
An integer from 1 to 10 that determines the compression quality.
For lossy formats (jpg and webp), lower settings will produce
smaller files with lower quality, while higher settings will inc-
rease quality at the cost of higher file size. A quality of 10 is
lossless for webp.
For png, lower settings will compress faster and produce larger
files, while higher settings will compress slower, but produce
smaller files. No effect on bmp images.

-s, --select
Enables an interactive selection mode where you may select the
Expand Down Expand Up @@ -408,14 +411,14 @@ int app( int argc, char** argv ) {
("h,help", "Print help and exit.")
("v,version", "Print version and exit.")
("x,xdisplay", "Sets the xdisplay to use", cxxopts::value<std::string>())
("f,format", "Sets the desired output format, by default maim will attempt to determine the desired output format automatically from the output file. If that fails it defaults to a lossless png format. Supports `png`, `jpg`, and `bmp`.", cxxopts::value<std::string>())
("f,format", "Sets the desired output format, by default maim will attempt to determine the desired output format automatically from the output file. If that fails it defaults to a lossless png format. Supports `png`, `jpg`, `bmp` and `webp`.", cxxopts::value<std::string>())
("i,window", "Sets the desired window to capture, defaults to the root window. Allows for an integer, hex, or `root` for input.", cxxopts::value<std::string>())
("g,geometry", "Sets the region to capture, uses local coordinates from the given window. So -g10x30-5+0 would represent the rectangle wxh+x+y where w=10, h=30, x=-5, and y=0. x and y are the upper left location of this rectangle.", cxxopts::value<std::string>())
("w,parent", "By default, maim assumes the --geometry values are in respect to the provided --window (or root if not provided). This parameter overrides this behavior by making the geometry be in respect to whatever window you provide to --parent. Allows for an integer, hex, or `root` for input.", cxxopts::value<std::string>())
("B,capturebackground", "By default, when capturing a window, maim will ignore anything beneath the specified window. This parameter overrides this and also captures elements underneath the window.")
("d,delay", "Sets the time in seconds to wait before taking a screenshot. Prints a simple message to show how many seconds are left before a screenshot is taken. See --quiet for muting this message.", cxxopts::value<float>()->implicit_value("5"))
("u,hidecursor", "By default maim super-imposes the cursor onto the image, you can disable that behavior with this flag.")
("m,quality", "An integer from 1 to 10 that determines the compression quality. 1 is the highest (and lossiest) compression available for the provided format. For example a setting of `1` with png (a loss‐ less format) would increase filesize and decrease decoding time. While a setting of `1` on a jpeg would create a pixel mush. No effect on bmp images.", cxxopts::value<int>())
("m,quality", "An integer from 1 to 10 that determines the compression quality. For lossy formats (jpg and webp), lower settings will produce smaller files with lower quality, while higher settings will increase quality at the cost of higher file size. A quality of 10 is lossless for webp. For png, lower settings will compress faster and produce larger files, while higher settings will compress slower, but produce smaller files. No effect on bmp images.", cxxopts::value<int>())
("s,select", "Enables an interactive selection mode where you may select the desired region or window before a screenshot is captured. Uses the settings below to determine the visuals and settings of slop.")
("b,bordersize", "Sets the selection rectangle's thickness.", cxxopts::value<float>())
("p,padding", "Sets the padding size for the selection, this can be negative.", cxxopts::value<float>())
Expand Down Expand Up @@ -473,8 +476,8 @@ int app( int argc, char** argv ) {

if ( !maimOptions->formatGiven && maimOptions->savepathGiven && maimOptions->savepath.find_last_of(".") != std::string::npos ) {
maimOptions->format = maimOptions->savepath.substr(maimOptions->savepath.find_last_of(".")+1);
if ( maimOptions->format != "png" && maimOptions->format != "jpg" && maimOptions->format != "jpeg" && maimOptions->format != "bmp") {
throw new std::invalid_argument("Unknown format type: `" + maimOptions->format + "`, only `png`, `jpg`, or `bmp` is allowed." );
if ( maimOptions->format != "png" && maimOptions->format != "jpg" && maimOptions->format != "jpeg" && maimOptions->format != "bmp" && maimOptions->format != "webp") {
throw new std::invalid_argument("Unknown format type: `" + maimOptions->format + "`, only `png`, `jpg`, `bmp` or `webp` is allowed." );
}
}
if ( !maimOptions->windowGiven ) {
Expand Down Expand Up @@ -557,44 +560,37 @@ int app( int argc, char** argv ) {
glm::ivec2 imageloc;
// Snapshot the image
XImage* image = x11->getImage( selection.id, px, py, selection.w, selection.h, imageloc);
if ( maimOptions->format == "png" ) {
// Convert it to an ARGB format, clipping it to the selection.
ARGBImage convert(image, imageloc, glm::vec4(px, py, selection.w, selection.h), 4, x11 );
if ( !maimOptions->hideCursor ) {
convert.blendCursor( x11 );
}
// Mask it if we're taking a picture of root
if ( selection.id == x11->root ) {
convert.mask(x11);
}
// Then output it in the desired format.

int num_channels;
if ( maimOptions->format == "png" || maimOptions->format == "webp" ) {
// Convert it to an ARGB format, clipping it to the selection
num_channels = 4;
} else {
// Otherwise (jpeg/bmp), convert to RGB, also clipping it to the selection
num_channels = 3;
}

ARGBImage convert(image, imageloc, glm::vec4(px, py, selection.w, selection.h), num_channels, x11 );

if ( !maimOptions->hideCursor ) {
convert.blendCursor( x11 );
}
// Mask it if we're taking a picture of root
if ( selection.id == x11->root ) {
convert.mask(x11);
}

// then output it into into the desired format
if (maimOptions->format == "png") {
convert.writePNG(*out, maimOptions->quality );
} else if ( maimOptions->format == "jpg" || maimOptions->format == "jpeg" ) {
// Convert it to a RGB format, clipping it to the selection.
ARGBImage convert(image, imageloc, glm::vec4(px, py, selection.w, selection.h), 3, x11 );
if ( !maimOptions->hideCursor ) {
convert.blendCursor( x11 );
}
// Mask it if we're taking a picture of root
if ( selection.id == x11->root ) {
convert.mask(x11);
}
// Then output it in the desired format.
convert.writeJPEG(*out, maimOptions->quality );
} else if (maimOptions->format == "bmp") {
// Convert it to a RGB format, clipping it to the selection.
ARGBImage convert(image, imageloc, glm::vec4(px, py, selection.w, selection.h), 3, x11 );
if ( !maimOptions->hideCursor ) {
convert.blendCursor( x11 );
}
// Mask it if we're taking a picture of root
if ( selection.id == x11->root ) {
convert.mask(x11);
}
// Then output it in the desired format.
} else if ( maimOptions->format == "bmp" ) {
convert.writeBMP(*out);

} else if ( maimOptions->format == "webp" ) {
convert.writeWEBP(*out, maimOptions->quality);
}

XDestroyImage( image );

if ( maimOptions->savepathGiven ) {
Expand Down