Compare commits

..

No commits in common. "25c91f5a77fcc7aabf7226a2ffee3817afdf49df" and "95bc91e0209ff0b5798d901f2dfd197f6d4311de" have entirely different histories.

12 changed files with 22 additions and 233 deletions

View File

@ -43,9 +43,7 @@ Build-only dependencies:
Runtime dependencies:
gtk+-3.0, glib>=2.64, pixman-1, shared-mime-info, libturbojpeg, libwebp +
Optional dependencies: lcms2, LibRaw, librsvg-2.0, xcursor, libheif, libtiff,
ExifTool, resvg (unstable API, needs to be requested explicitly) +
Runtime dependencies for reverse image search:
xdg-utils, cURL, jq
ExifTool, resvg (unstable API, needs to be requested explicitly)
$ git clone --recursive https://git.janouch.name/p/fiv.git
$ meson builddir
@ -57,8 +55,7 @@ direct installations via `ninja install`. To test the program:
$ meson devenv fiv
The lossless JPEG cropper and reverse image search are intended to be invoked
from a context menu.
The lossless JPEG cropper is intended to be invoked from a context menu.
Windows
~~~~~~~

View File

@ -396,15 +396,6 @@ on_chooser_activate(GtkMenuItem *item, gpointer user_data)
GtkWidget *dialog = gtk_app_chooser_dialog_new_for_content_type(window,
GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, ctx->content_type);
g_clear_object(&window);
#if 0
// This exists as a concept in mimeapps.list, but GNOME infuriatingly
// infers it from the last used application if missing.
gtk_app_chooser_widget_set_show_default(
GTK_APP_CHOOSER_WIDGET(gtk_app_chooser_dialog_get_widget(
GTK_APP_CHOOSER_DIALOG(dialog))), TRUE);
#endif
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) {
ctx->app_info = gtk_app_chooser_get_app_info(GTK_APP_CHOOSER(dialog));
open_context_launch(GTK_WIDGET(item), ctx);

View File

@ -1,7 +1,7 @@
//
// fiv-io.c: image operations
//
// Copyright (c) 2021 - 2023, Přemysl Eric Janouch <p@janouch.name>
// Copyright (c) 2021 - 2022, Přemysl Eric Janouch <p@janouch.name>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted.
@ -633,8 +633,9 @@ load_wuffs_frame(struct load_wuffs_frame_context *ctx, GError **error)
if (!wuffs_base__status__is_ok(&status)) {
set_error(error, wuffs_base__status__message(&status));
// The PNG decoder, at minimum, will flush any pixel data upon
// finding out that the input is truncated, so accept whatever we get.
// The PNG decoder, at minimum, will flush any pixel data, so use them.
if (status.repr != wuffs_base__suspension__short_read)
goto fail;
}
if (ctx->target) {
@ -2958,88 +2959,6 @@ fiv_io_deserialize(GBytes *bytes, guint64 *user_data)
return surface;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static cairo_status_t
write_to_byte_array(
void *closure, const unsigned char *data, unsigned int length)
{
g_byte_array_append(closure, data, length);
return CAIRO_STATUS_SUCCESS;
}
GBytes *
fiv_io_serialize_for_search(cairo_surface_t *surface, GError **error)
{
g_return_val_if_fail(
surface && cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE,
NULL);
cairo_format_t format = cairo_image_surface_get_format(surface);
if (format == CAIRO_FORMAT_ARGB32) {
const uint32_t *data =
(const uint32_t *) cairo_image_surface_get_data(surface);
bool all_solid = true;
for (size_t len = cairo_image_surface_get_width(surface) *
cairo_image_surface_get_height(surface); len--; ) {
if ((data[len] >> 24) != 0xFF)
all_solid = false;
}
if (all_solid)
format = CAIRO_FORMAT_RGB24;
}
if (format != CAIRO_FORMAT_RGB24) {
#if CAIRO_HAS_PNG_FUNCTIONS
GByteArray *ba = g_byte_array_new();
cairo_status_t status =
cairo_surface_write_to_png_stream(surface, write_to_byte_array, ba);
if (status == CAIRO_STATUS_SUCCESS)
return g_byte_array_free_to_bytes(ba);
g_byte_array_unref(ba);
#endif
// Last resort: remove transparency by painting over black.
cairo_surface_t *converted =
cairo_image_surface_create(CAIRO_FORMAT_RGB24,
cairo_image_surface_get_width(surface),
cairo_image_surface_get_height(surface));
cairo_t *cr = cairo_create(converted);
cairo_set_source_surface(cr, surface, 0, 0);
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
cairo_paint(cr);
cairo_destroy(cr);
GBytes *result = fiv_io_serialize_for_search(converted, error);
cairo_surface_destroy(converted);
return result;
}
tjhandle enc = tjInitCompress();
if (!enc) {
set_error(error, tjGetErrorStr2(enc));
return NULL;
}
unsigned char *jpeg = NULL;
unsigned long length = 0;
if (tjCompress2(enc, cairo_image_surface_get_data(surface),
cairo_image_surface_get_width(surface),
cairo_image_surface_get_stride(surface),
cairo_image_surface_get_height(surface),
(G_BYTE_ORDER == G_LITTLE_ENDIAN ? TJPF_BGRX : TJPF_XRGB),
&jpeg, &length, TJSAMP_444, 90, 0)) {
set_error(error, tjGetErrorStr2(enc));
tjFree(jpeg);
tjDestroy(enc);
return NULL;
}
tjDestroy(enc);
return g_bytes_new_with_free_func(
jpeg, length, (GDestroyNotify) tjFree, jpeg);
}
// --- Filesystem --------------------------------------------------------------
#include "xdg.h"

View File

@ -1,7 +1,7 @@
//
// fiv-io.h: image operations
//
// Copyright (c) 2021 - 2023, Přemysl Eric Janouch <p@janouch.name>
// Copyright (c) 2021 - 2022, Přemysl Eric Janouch <p@janouch.name>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted.
@ -107,8 +107,6 @@ enum { FIV_IO_SERIALIZE_LOW_QUALITY = 1 << 0 };
void fiv_io_serialize_to_stdout(cairo_surface_t *surface, guint64 user_data);
cairo_surface_t *fiv_io_deserialize(GBytes *bytes, guint64 *user_data);
GBytes *fiv_io_serialize_for_search(cairo_surface_t *surface, GError **error);
// --- Filesystem --------------------------------------------------------------
typedef enum _FivIoModelSort {

View File

@ -1,9 +0,0 @@
#!/bin/sh -e
if [ "$#" -ne 2 ]; then
echo "Usage: $0 SEARCH-ENGINE-URI-PREFIX {PATH | URI}" >&2
exit 1
fi
xdg-open "$1$(fiv --thumbnail-for-search large "$2" \
| curl --silent --show-error --upload-file - https://transfer.sh/image \
| jq --slurp --raw-input --raw-output @uri)"

View File

@ -1,10 +0,0 @@
[Desktop Entry]
Type=Application
Name=fiv @NAME@ Reverse Image Search
GenericName=@NAME@ Reverse Image Search
Icon=fiv
Exec=fiv-reverse-search "@URL@" %u
NoDisplay=true
Terminal=false
Categories=Graphics;2DGraphics;
MimeType=image/png;image/bmp;image/gif;image/x-tga;image/jpeg;image/webp;

View File

@ -1,7 +1,7 @@
//
// fiv-thumbnail.c: thumbnail management
//
// Copyright (c) 2021 - 2023, Přemysl Eric Janouch <p@janouch.name>
// Copyright (c) 2021 - 2022, Přemysl Eric Janouch <p@janouch.name>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted.
@ -421,29 +421,6 @@ save_thumbnail(cairo_surface_t *thumbnail, const char *path, GString *thum)
WebPDataClear(&assembled);
}
cairo_surface_t *
fiv_thumbnail_produce_for_search(
GFile *target, FivThumbnailSize max_size, GError **error)
{
g_return_val_if_fail(max_size >= FIV_THUMBNAIL_SIZE_MIN &&
max_size <= FIV_THUMBNAIL_SIZE_MAX, NULL);
GBytes *data = g_file_load_bytes(target, NULL, NULL, error);
if (!data)
return NULL;
gboolean color_managed = FALSE;
cairo_surface_t *surface = render(target, data, &color_managed, error);
if (!surface)
return NULL;
// TODO(p): Might want to keep this a square.
cairo_surface_t *result =
adjust_thumbnail(surface, fiv_thumbnail_sizes[max_size].size);
cairo_surface_destroy(surface);
return result;
}
static cairo_surface_t *
produce_fallback(GFile *target, FivThumbnailSize size, GError **error)
{
@ -482,7 +459,7 @@ cairo_surface_t *
fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, GError **error)
{
g_return_val_if_fail(max_size >= FIV_THUMBNAIL_SIZE_MIN &&
max_size <= FIV_THUMBNAIL_SIZE_MAX, NULL);
max_size <= FIV_THUMBNAIL_SIZE_MAX, FALSE);
// Don't save thumbnails for FUSE mounts, such as sftp://.
// Moreover, it doesn't make sense to save thumbnails of thumbnails.

View File

@ -1,7 +1,7 @@
//
// fiv-thumbnail.h: thumbnail management
//
// Copyright (c) 2021 - 2023, Přemysl Eric Janouch <p@janouch.name>
// Copyright (c) 2021 - 2022, Přemysl Eric Janouch <p@janouch.name>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted.
@ -62,10 +62,6 @@ cairo_surface_t *fiv_thumbnail_extract(
cairo_surface_t *fiv_thumbnail_produce(
GFile *target, FivThumbnailSize max_size, GError **error);
/// Like fiv_thumbnail_produce(), but skips the cache.
cairo_surface_t *fiv_thumbnail_produce_for_search(
GFile *target, FivThumbnailSize max_size, GError **error);
/// Retrieves a thumbnail of the most appropriate quality and resolution
/// for the target file.
cairo_surface_t *fiv_thumbnail_lookup(

View File

@ -1,8 +1,4 @@
#!/bin/sh -e
fiv=${DESTDIR:+$DESTDIR/}'@FIV@'
desktopdir=${DESTDIR:+$DESTDIR/}'@DESKTOPDIR@'
types=$("$fiv" --list-supported-media-types | tr '\n' ';')
for desktop in @DESKTOPS@
do sed -i "s|^MimeType=.*|MimeType=$types|" "$desktopdir"/"$desktop"
done
sed -i "s|^MimeType=.*|MimeType=$(
"${DESTDIR:+$DESTDIR/}"'@EXE@' --list-supported-media-types | tr '\n' ';'
)|" "${DESTDIR:+$DESTDIR/}"'@DESKTOP@'

47
fiv.c
View File

@ -1,7 +1,7 @@
//
// fiv.c: fuck-if-I-know-how-to-name-it image browser and viewer
//
// Copyright (c) 2021 - 2023, Přemysl Eric Janouch <p@janouch.name>
// Copyright (c) 2021 - 2022, Přemysl Eric Janouch <p@janouch.name>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted.
@ -1934,8 +1934,8 @@ static const char stylesheet[] = "@define-color fiv-tile @content_view_bg; \
} \
.fiv-information label { padding: 0 4px; }";
static FivThumbnailSize
output_thumbnail_prologue(gchar **uris, const char *size_arg)
static void
output_thumbnail(gchar **uris, gboolean extract, const char *size_arg)
{
if (!uris)
exit_fatal("No path given");
@ -1956,38 +1956,6 @@ output_thumbnail_prologue(gchar **uris, const char *size_arg)
#ifdef G_OS_WIN32
_setmode(fileno(stdout), _O_BINARY);
#endif
return size;
}
static void
output_thumbnail_for_search(gchar **uris, const char *size_arg)
{
FivThumbnailSize size = output_thumbnail_prologue(uris, size_arg);
GError *error = NULL;
GFile *file = g_file_new_for_uri(uris[0]);
cairo_surface_t *surface = NULL;
GBytes *bytes = NULL;
if ((surface = fiv_thumbnail_produce(file, size, &error)) &&
(bytes = fiv_io_serialize_for_search(surface, &error))) {
fwrite(
g_bytes_get_data(bytes, NULL), 1, g_bytes_get_size(bytes), stdout);
g_bytes_unref(bytes);
} else {
g_assert(error != NULL);
}
g_object_unref(file);
if (error)
exit_fatal("%s", error->message);
cairo_surface_destroy(surface);
}
static void
output_thumbnail(gchar **uris, gboolean extract, const char *size_arg)
{
FivThumbnailSize size = output_thumbnail_prologue(uris, size_arg);
GError *error = NULL;
GFile *file = g_file_new_for_uri(uris[0]);
@ -2013,7 +1981,7 @@ main(int argc, char *argv[])
{
gboolean show_version = FALSE, show_supported_media_types = FALSE,
invalidate_cache = FALSE, browse = FALSE, extract_thumbnail = FALSE;
gchar **args = NULL, *thumbnail_size = NULL, *thumbnail_size_search = NULL;
gchar **args = NULL, *thumbnail_size = NULL;
const GOptionEntry options[] = {
{G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &args,
NULL, "[PATH | URI]..."},
@ -2023,9 +1991,6 @@ main(int argc, char *argv[])
{"browse", 0, G_OPTION_FLAG_IN_MAIN,
G_OPTION_ARG_NONE, &browse,
"Start in filesystem browsing mode", NULL},
{"thumbnail-for-search", 0, G_OPTION_FLAG_IN_MAIN,
G_OPTION_ARG_STRING, &thumbnail_size_search,
"Output an image file suitable for searching by content", "SIZE"},
{"extract-thumbnail", 0, G_OPTION_FLAG_IN_MAIN,
G_OPTION_ARG_NONE, &extract_thumbnail,
"Output any embedded thumbnail (superseding --thumbnail)", NULL},
@ -2067,10 +2032,6 @@ main(int argc, char *argv[])
args[i] = g_file_get_uri(resolved);
g_object_unref(resolved);
}
if (thumbnail_size_search) {
output_thumbnail_for_search(args, thumbnail_size_search);
return 0;
}
if (extract_thumbnail || thumbnail_size) {
output_thumbnail(args, extract_thumbnail, thumbnail_size);
return 0;

View File

@ -35,12 +35,12 @@ dependencies = [
dependency('gtk+-3.0'),
dependency('pixman-1'),
# Wuffs is included as a submodule.
dependency('libturbojpeg'),
dependency('libwebp'),
dependency('libwebpdemux'),
dependency('libwebpdecoder', required : false),
dependency('libwebpmux'),
# Wuffs is included as a submodule.
lcms2,
libjpegqs,
@ -251,32 +251,6 @@ if not win32
install_dir : get_option('datadir') / 'applications')
endforeach
# TODO(p): Consider moving this to /usr/share or /usr/lib.
install_data('fiv-reverse-search',
install_dir : get_option('bindir'))
# As usual, handling generated files in Meson is a fucking pain.
updatable_desktops = [application_ns + 'fiv.desktop']
foreach name, uri : {
'Google' : 'https://lens.google.com/uploadbyurl?url=',
'Bing' : 'https://www.bing.com/images/searchbyimage?cbir=sbi&imgurl=',
'Yandex' : 'https://yandex.com/images/search?rpt=imageview&url=',
'TinEye' : 'https://tineye.com/search?url=',
'SauceNAO' : 'https://saucenao.com/search.php?url=',
'IQDB' : 'https://iqdb.org/?url=',
}
desktop = 'fiv-reverse-search-' + name.to_lower() + '.desktop'
updatable_desktops += application_ns + desktop
test(desktop, dfv, args : configure_file(
input : 'fiv-reverse-search.desktop.in',
output : application_ns + desktop,
configuration : {'NAME' : name, 'URL' : uri},
install : true,
install_dir : get_option('datadir') / 'applications',
))
endforeach
# With gdk-pixbuf, fiv.desktop depends on currently installed modules;
# the package manager needs to be told to run this script as necessary.
dynamic_desktops = gdkpixbuf.found()
@ -285,10 +259,9 @@ if not win32
input : 'fiv-update-desktop-files.in',
output : 'fiv-update-desktop-files',
configuration : {
'FIV' : get_option('prefix') / get_option('bindir') / exe.name(),
'DESKTOPDIR' : get_option('prefix') /
get_option('datadir') / 'applications',
'DESKTOPS' : ' \\\n\t'.join(updatable_desktops),
'EXE' : get_option('prefix') / get_option('bindir') / exe.name(),
'DESKTOP' : get_option('prefix') / get_option('datadir') \
/ 'applications' / application_ns + 'fiv.desktop',
},
install : dynamic_desktops,
install_dir : get_option('bindir'))

@ -1 +1 @@
Subproject commit 40ff91b31b3286aa92fd3cb4656975b275ef8b10
Subproject commit 0b70377a12c81cf88afe259f2b4085eb7a59eb59