2021-10-23 00:51:20 +08:00
|
|
|
#include "icon.h"
|
|
|
|
|
|
|
|
#include <assert.h>
|
|
|
|
#include <stdbool.h>
|
2022-01-16 08:45:36 +08:00
|
|
|
#include <libavcodec/avcodec.h>
|
2021-10-23 00:51:20 +08:00
|
|
|
#include <libavformat/avformat.h>
|
|
|
|
#include <libavutil/pixdesc.h>
|
|
|
|
#include <libavutil/pixfmt.h>
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
#include "compat.h"
|
2021-11-11 23:12:17 +08:00
|
|
|
#include "util/file.h"
|
2021-10-23 00:51:20 +08:00
|
|
|
#include "util/log.h"
|
2021-11-13 06:12:51 +08:00
|
|
|
#include "util/str.h"
|
2021-10-23 00:51:20 +08:00
|
|
|
|
|
|
|
#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__
|
2021-11-13 06:08:19 +08:00
|
|
|
char *icon_path = sc_str_from_wchars(icon_path_env);
|
2021-10-23 00:51:20 +08:00
|
|
|
#else
|
|
|
|
char *icon_path = strdup(icon_path_env);
|
|
|
|
#endif
|
|
|
|
if (!icon_path) {
|
2021-11-25 05:06:11 +08:00
|
|
|
LOG_OOM();
|
2021-10-23 00:51:20 +08:00
|
|
|
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) {
|
2021-11-25 05:06:11 +08:00
|
|
|
LOG_OOM();
|
2021-10-23 00:51:20 +08:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
#else
|
2021-11-11 23:21:07 +08:00
|
|
|
char *icon_path = sc_file_get_local_path(SCRCPY_PORTABLE_ICON_FILENAME);
|
2021-10-23 00:51:20 +08:00
|
|
|
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) {
|
2021-11-25 05:06:11 +08:00
|
|
|
LOG_OOM();
|
2021-10-23 00:51:20 +08:00
|
|
|
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;
|
|
|
|
|
2022-01-16 08:45:36 +08:00
|
|
|
const AVCodec *codec = avcodec_find_decoder(params->codec_id);
|
2021-10-23 00:51:20 +08:00
|
|
|
if (!codec) {
|
|
|
|
LOGE("Could not find image decoder");
|
|
|
|
goto close_input;
|
|
|
|
}
|
|
|
|
|
|
|
|
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
|
|
|
|
if (!codec_ctx) {
|
2021-11-25 05:06:11 +08:00
|
|
|
LOG_OOM();
|
2021-10-23 00:51:20 +08:00
|
|
|
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) {
|
2021-11-25 05:06:11 +08:00
|
|
|
LOG_OOM();
|
2021-10-23 00:51:20 +08:00
|
|
|
goto close_codec;
|
|
|
|
}
|
|
|
|
|
|
|
|
AVPacket *packet = av_packet_alloc();
|
|
|
|
if (!packet) {
|
2021-11-25 05:06:11 +08:00
|
|
|
LOG_OOM();
|
2021-10-23 00:51:20 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-11-14 22:15:34 +08:00
|
|
|
#if !SDL_VERSION_ATLEAST(2, 0, 10)
|
|
|
|
// SDL_PixelFormatEnum has been introduced in SDL 2.0.10. Use int for older SDL
|
|
|
|
// versions.
|
|
|
|
typedef int SDL_PixelFormatEnum;
|
|
|
|
#endif
|
|
|
|
|
2021-10-23 00:51:20 +08:00
|
|
|
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;
|
2021-11-14 22:15:34 +08:00
|
|
|
#if SDL_VERSION_ATLEAST(2, 0, 12)
|
2021-10-23 00:51:20 +08:00
|
|
|
case AV_PIX_FMT_BGR444BE: return SDL_PIXELFORMAT_BGR444;
|
2021-11-14 22:15:34 +08:00
|
|
|
#endif
|
2021-10-23 00:51:20 +08:00
|
|
|
case AV_PIX_FMT_PAL8: return SDL_PIXELFORMAT_INDEX8;
|
2021-10-23 00:51:20 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-10-23 00:51:20 +08:00
|
|
|
bool is_packed = !(desc->flags & AV_PIX_FMT_FLAG_PLANAR);
|
|
|
|
if (!is_packed) {
|
|
|
|
LOGE("Could not load non-packed icon");
|
2021-10-23 00:51:20 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-10-23 00:51:20 +08:00
|
|
|
if (frame->format == AV_PIX_FMT_PAL8) {
|
|
|
|
// Initialize the SDL palette
|
|
|
|
uint8_t *data = frame->data[1];
|
|
|
|
SDL_Color colors[256];
|
|
|
|
for (int i = 0; i < 256; ++i) {
|
|
|
|
SDL_Color *color = &colors[i];
|
|
|
|
|
|
|
|
// The palette is transported in AVFrame.data[1], is 1024 bytes
|
|
|
|
// long (256 4-byte entries) and is formatted the same as in
|
|
|
|
// AV_PIX_FMT_RGB32 described above (i.e., it is also
|
|
|
|
// endian-specific).
|
|
|
|
// <https://ffmpeg.org/doxygen/4.1/pixfmt_8h.html#a9a8e335cf3be472042bc9f0cf80cd4c5>
|
|
|
|
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
|
|
|
|
color->a = data[i * 4];
|
|
|
|
color->r = data[i * 4 + 1];
|
|
|
|
color->g = data[i * 4 + 2];
|
|
|
|
color->b = data[i * 4 + 3];
|
|
|
|
#else
|
|
|
|
color->a = data[i * 4 + 3];
|
|
|
|
color->r = data[i * 4 + 2];
|
|
|
|
color->g = data[i * 4 + 1];
|
|
|
|
color->b = data[i * 4];
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
SDL_Palette *palette = surface->format->palette;
|
|
|
|
assert(palette);
|
|
|
|
int ret = SDL_SetPaletteColors(palette, colors, 0, 256);
|
|
|
|
if (ret) {
|
|
|
|
LOGE("Could not set palette colors");
|
|
|
|
SDL_FreeSurface(surface);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-23 00:51:20 +08:00
|
|
|
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);
|
|
|
|
}
|