From 0e4564da03df723dfe799cbcf15d26b3bdaaf6a5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 22 Oct 2021 18:51:20 +0200 Subject: [PATCH] Add icon loader Add helper to load icons from image files via FFmpeg. --- app/meson.build | 1 + app/src/icon.c | 247 ++++++++++++++++++++++++++++++++++++++++++++++++ app/src/icon.h | 16 ++++ 3 files changed, 264 insertions(+) create mode 100644 app/src/icon.c create mode 100644 app/src/icon.h diff --git a/app/meson.build b/app/meson.build index f5345803..698120a5 100644 --- a/app/meson.build +++ b/app/meson.build @@ -9,6 +9,7 @@ src = [ 'src/decoder.c', 'src/device_msg.c', 'src/event_converter.c', + 'src/icon.c', 'src/file_handler.c', 'src/fps_counter.c', 'src/frame_buffer.c', diff --git a/app/src/icon.c b/app/src/icon.c new file mode 100644 index 00000000..119c5eeb --- /dev/null +++ b/app/src/icon.c @@ -0,0 +1,247 @@ +#include "icon.h" + +#include +#include +#include +#include +#include + +#include "config.h" +#include "compat.h" +#include "util/log.h" +#include "util/process.h" +#include "util/str_util.h" + +#define SCRCPY_PORTABLE_ICON_FILENAME "icon.png" +#define SCRCPY_DEFAULT_ICON_PATH \ + PREFIX "/share/icons/hicolor/256x256/apps/scrcpy.png" + +static char * +get_icon_path(void) { +#ifdef __WINDOWS__ + const wchar_t *icon_path_env = _wgetenv(L"SCRCPY_ICON_PATH"); +#else + const char *icon_path_env = getenv("SCRCPY_ICON_PATH"); +#endif + if (icon_path_env) { + // if the envvar is set, use it +#ifdef __WINDOWS__ + char *icon_path = utf8_from_wide_char(icon_path_env); +#else + char *icon_path = strdup(icon_path_env); +#endif + if (!icon_path) { + LOGE("Could not allocate memory"); + return NULL; + } + LOGD("Using SCRCPY_ICON_PATH: %s", icon_path); + return icon_path; + } + +#ifndef PORTABLE + LOGD("Using icon: " SCRCPY_DEFAULT_ICON_PATH); + char *icon_path = strdup(SCRCPY_DEFAULT_ICON_PATH); + if (!icon_path) { + LOGE("Could not allocate memory"); + return NULL; + } +#else + char *icon_path = get_local_file_path(SCRCPY_PORTABLE_ICON_FILENAME); + if (!icon_path) { + LOGE("Could not get icon path"); + return NULL; + } + LOGD("Using icon (portable): %s", icon_path); +#endif + + return icon_path; +} + +static AVFrame * +decode_image(const char *path) { + AVFrame *result = NULL; + + AVFormatContext *ctx = avformat_alloc_context(); + if (!ctx) { + LOGE("Could not allocate image decoder context"); + return NULL; + } + + if (avformat_open_input(&ctx, path, NULL, NULL) < 0) { + LOGE("Could not open image codec: %s", path); + goto free_ctx; + } + + if (avformat_find_stream_info(ctx, NULL) < 0) { + LOGE("Could not find image stream info"); + goto close_input; + } + + int stream = av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); + if (stream < 0 ) { + LOGE("Could not find best image stream"); + goto close_input; + } + + AVCodecParameters *params = ctx->streams[stream]->codecpar; + + AVCodec *codec = avcodec_find_decoder(params->codec_id); + if (!codec) { + LOGE("Could not find image decoder"); + goto close_input; + } + + AVCodecContext *codec_ctx = avcodec_alloc_context3(codec); + if (!codec_ctx) { + LOGE("Could not allocate codec context"); + goto close_input; + } + + if (avcodec_parameters_to_context(codec_ctx, params) < 0) { + LOGE("Could not fill codec context"); + goto free_codec_ctx; + } + + if (avcodec_open2(codec_ctx, codec, NULL) < 0) { + LOGE("Could not open image codec"); + goto free_codec_ctx; + } + + AVFrame *frame = av_frame_alloc(); + if (!frame) { + LOGE("Could not allocate frame"); + goto close_codec; + } + + AVPacket *packet = av_packet_alloc(); + if (!packet) { + LOGE("Could not allocate packet"); + av_frame_free(&frame); + goto close_codec; + } + + if (av_read_frame(ctx, packet) < 0) { + LOGE("Could not read frame"); + av_packet_free(&packet); + av_frame_free(&frame); + goto close_codec; + } + + int ret; + if ((ret = avcodec_send_packet(codec_ctx, packet)) < 0) { + LOGE("Could not send icon packet: %d", ret); + av_packet_free(&packet); + av_frame_free(&frame); + goto close_codec; + } + + if ((ret = avcodec_receive_frame(codec_ctx, frame)) != 0) { + LOGE("Could not receive icon frame: %d", ret); + av_packet_free(&packet); + av_frame_free(&frame); + goto close_codec; + } + + av_packet_free(&packet); + + result = frame; + +close_codec: + avcodec_close(codec_ctx); +free_codec_ctx: + avcodec_free_context(&codec_ctx); +close_input: + avformat_close_input(&ctx); +free_ctx: + avformat_free_context(ctx); + + return result; +} + +static SDL_PixelFormatEnum +to_sdl_pixel_format(enum AVPixelFormat fmt) { + switch (fmt) { + case AV_PIX_FMT_RGB24: return SDL_PIXELFORMAT_RGB24; + case AV_PIX_FMT_BGR24: return SDL_PIXELFORMAT_BGR24; + case AV_PIX_FMT_ARGB: return SDL_PIXELFORMAT_ARGB32; + case AV_PIX_FMT_RGBA: return SDL_PIXELFORMAT_RGBA32; + case AV_PIX_FMT_ABGR: return SDL_PIXELFORMAT_ABGR32; + case AV_PIX_FMT_BGRA: return SDL_PIXELFORMAT_BGRA32; + case AV_PIX_FMT_RGB565BE: return SDL_PIXELFORMAT_RGB565; + case AV_PIX_FMT_RGB555BE: return SDL_PIXELFORMAT_RGB555; + case AV_PIX_FMT_BGR565BE: return SDL_PIXELFORMAT_BGR565; + case AV_PIX_FMT_BGR555BE: return SDL_PIXELFORMAT_BGR555; + case AV_PIX_FMT_RGB444BE: return SDL_PIXELFORMAT_RGB444; + case AV_PIX_FMT_BGR444BE: return SDL_PIXELFORMAT_BGR444; + default: return SDL_PIXELFORMAT_UNKNOWN; + } +} + +static SDL_Surface * +load_from_path(const char *path) { + AVFrame *frame = decode_image(path); + if (!frame) { + return NULL; + } + + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format); + if (!desc) { + LOGE("Could not get icon format descriptor"); + goto error; + } + + bool is_packed_rgb = desc->flags & AV_PIX_FMT_FLAG_RGB + && !(desc->flags & AV_PIX_FMT_FLAG_PLANAR); + if (!is_packed_rgb) { + LOGE("Could not load non-RGB icon"); + goto error; + } + + SDL_PixelFormatEnum format = to_sdl_pixel_format(frame->format); + if (format == SDL_PIXELFORMAT_UNKNOWN) { + LOGE("Unsupported icon pixel format: %s (%d)", desc->name, + frame->format); + goto error; + } + + int bits_per_pixel = av_get_bits_per_pixel(desc); + SDL_Surface *surface = + SDL_CreateRGBSurfaceWithFormatFrom(frame->data[0], + frame->width, frame->height, + bits_per_pixel, + frame->linesize[0], + format); + + if (!surface) { + LOGE("Could not create icon surface"); + goto error; + } + + surface->userdata = frame; // frame owns the data + + return surface; + +error: + av_frame_free(&frame); + return NULL; +} + +SDL_Surface * +scrcpy_icon_load() { + char *icon_path = get_icon_path(); + if (!icon_path) { + return NULL; + } + + SDL_Surface *icon = load_from_path(icon_path); + free(icon_path); + return icon; +} + +void +scrcpy_icon_destroy(SDL_Surface *icon) { + AVFrame *frame = icon->userdata; + assert(frame); + av_frame_free(&frame); + SDL_FreeSurface(icon); +} diff --git a/app/src/icon.h b/app/src/icon.h new file mode 100644 index 00000000..8df53671 --- /dev/null +++ b/app/src/icon.h @@ -0,0 +1,16 @@ +#ifndef ICON_H +#define ICON_H + +#include "common.h" + +#include +#include +#include + +SDL_Surface * +scrcpy_icon_load(void); + +void +scrcpy_icon_destroy(SDL_Surface *icon); + +#endif