From 289ea9586cc4283222ed040ae8d813405771ccfa Mon Sep 17 00:00:00 2001 From: lifegpc Date: Sun, 27 Aug 2023 08:31:24 +0800 Subject: [PATCH] Compelete thumbnail generate via ffmpeg API --- extensions/thumbnail/thumbnail.c | 135 +++++++++++++++++++++++++++---- extensions/thumbnail/thumbnail.h | 11 ++- thumbnail/base.ts | 8 ++ thumbnail/ffmpeg_api.ts | 16 +++- 4 files changed, 150 insertions(+), 20 deletions(-) diff --git a/extensions/thumbnail/thumbnail.c b/extensions/thumbnail/thumbnail.c index eea51ca..ee29d67 100644 --- a/extensions/thumbnail/thumbnail.c +++ b/extensions/thumbnail/thumbnail.c @@ -5,12 +5,12 @@ #include "libswscale/swscale.h" #include -PUBLIC_API void thumbnail_fferror(int e, char* buf, size_t bufsize) { +void thumbnail_fferror(int e, char* buf, size_t bufsize) { if (!buf) return; av_make_error_string(buf, bufsize, e); } -int thumbnail_convert_frame(THUMBNAIL_ERROR* err, AVFrame* ifr, AVFrame** ofr, AVCodecContext* occ, THUMBNAIL_METHOD method) { +int thumbnail_convert_frame(THUMBNAIL_ERROR* err, AVFrame* ifr, AVFrame** ofr, AVCodecContext* occ, THUMBNAIL_METHOD method, ALIGN_METHOD align) { if (!err || !ifr || !ofr) return 1; int re = 0; struct SwsContext* sws = NULL; @@ -20,17 +20,30 @@ int thumbnail_convert_frame(THUMBNAIL_ERROR* err, AVFrame* ifr, AVFrame** ofr, A re = 1; goto end; } - int theight = ifr->height * occ->width / ifr->width; - char simple_way = 0; - if (occ->height == theight || method == THUMBNAIL_FILL) { - simple_way = 1; - } - if (simple_way) { + if (method == THUMBNAIL_FILL) { fr->width = occ->width; fr->height = occ->height; - fr->format = occ->pix_fmt; - fr->sample_aspect_ratio = ifr->sample_aspect_ratio; + } else if (method == THUMBNAIL_COVER) { + int twidth = ifr->width * occ->height / ifr->height; + if (twidth > occ->width) { + fr->width = twidth; + fr->height = occ->height; + } else { + fr->width = occ->width; + fr->height = ifr->height * occ->width / ifr->width; + } + } else if (method == THUMBNAIL_CONTAIN) { + int twidth = ifr->width * occ->height / ifr->height; + if (twidth > occ->width) { + fr->width = occ->width; + fr->height = ifr->height * occ->width / ifr->width; + } else { + fr->width = twidth; + fr->height = occ->height; + } } + fr->format = occ->pix_fmt; + fr->sample_aspect_ratio = ifr->sample_aspect_ratio; if ((err->fferr = av_frame_get_buffer(fr, 0)) < 0) { err->e = THUMBNAIL_FFMPEG_ERROR; re = 1; @@ -43,17 +56,94 @@ int thumbnail_convert_frame(THUMBNAIL_ERROR* err, AVFrame* ifr, AVFrame** ofr, A av_log(NULL, AV_LOG_ERROR, "Failed to make writeable for output frame: %s\n", av_err2str(err->fferr)); goto end; } - if (simple_way) { - if (!(sws = sws_getContext(ifr->width, ifr->height, (enum AVPixelFormat)ifr->format, fr->width, fr->height, (enum AVPixelFormat)fr->format, SWS_BILINEAR, NULL, NULL, NULL))) { - err->e = THUMBNAIL_UNABLE_SCALE; + if (!(sws = sws_getContext(ifr->width, ifr->height, (enum AVPixelFormat)ifr->format, fr->width, fr->height, (enum AVPixelFormat)fr->format, SWS_BILINEAR, NULL, NULL, NULL))) { + err->e = THUMBNAIL_UNABLE_SCALE; + re = 1; + goto end; + } + if ((err->fferr = sws_scale(sws, (const uint8_t* const*)ifr->data, ifr->linesize, 0, ifr->height, fr->data, fr->linesize)) < 0) { + err->e = THUMBNAIL_UNABLE_SCALE; + re = 1; + goto end; + } + if (method != THUMBNAIL_FILL) { + if (!(fr2 = av_frame_alloc())) { + err->e = THUMBNAIL_OOM; re = 1; goto end; } - if ((err->fferr = sws_scale(sws, (const uint8_t* const*)ifr->data, ifr->linesize, 0, ifr->height, fr->data, fr->linesize)) < 0) { - err->e = THUMBNAIL_UNABLE_SCALE; + fr2->width = occ->width; + fr2->height = occ->height; + fr2->format = occ->pix_fmt; + fr2->sample_aspect_ratio = occ->sample_aspect_ratio; + if ((err->fferr = av_frame_get_buffer(fr2, 0)) < 0) { + err->e = THUMBNAIL_FFMPEG_ERROR; + av_log(NULL, AV_LOG_ERROR, "Failed to get buffer for output frame: %s\n", av_err2str(err->fferr)); re = 1; goto end; } + if ((err->fferr = av_frame_make_writable(fr2)) < 0) { + err->e = THUMBNAIL_FFMPEG_ERROR; + av_log(NULL, AV_LOG_ERROR, "Failed to make writeable for output frame: %s\n", av_err2str(err->fferr)); + re = 1; + goto end; + } + int width = 0, height = 0, x1 = 0, y1 = 0, i = 0, j = 0; + if (method == THUMBNAIL_COVER) { + width = occ->width; + height = occ->height; + if (fr->width == occ->width) { + if (align == ALIGN_CENTER) { + y1 = (fr->height - occ->height) / 2; + } else if (align == ALIGN_BOTTOM) { + y1 = fr->height - occ->height; + } + } else if (fr->height == occ->height) { + if (align == ALIGN_CENTER) { + x1 = (fr->width - occ->width) / 2; + } else if (align == ALIGN_RIGHT) { + x1 = fr->width - occ->width; + } + } + for (i = 0; i < height; i++) { + memcpy(fr2->data[0] + i * fr2->linesize[0], fr->data[0] + (i + y1) * fr->linesize[0] + x1, width); + } + for (j = 1; j < 3; j++) { + for (i = 0; i < height / 2; i++) { + memcpy(fr2->data[j] + i * fr2->linesize[j], fr->data[j] + (i + y1 / 2) * fr->linesize[j] + x1 / 2, width / 2); + } + } + } else if (method == THUMBNAIL_CONTAIN) { + width = fr->width; + height = fr->height; + if (fr->width == occ->width) { + if (align == ALIGN_CENTER) { + y1 = (occ->height - fr->height) / 2; + } else if (align == ALIGN_BOTTOM) { + y1 = occ->height - fr->height; + } + } else { + if (align == ALIGN_CENTER) { + x1 = (occ->width - fr->width) / 2; + } else if (align == ALIGN_RIGHT) { + x1 = occ->width - fr->width; + } + } + memset(fr2->data[0], 255, fr2->height * fr2->linesize[0]); + memset(fr2->data[1], 128, fr2->height / 2 * fr2->linesize[1]); + memset(fr2->data[2], 128, fr2->height / 2 * fr2->linesize[2]); + for (i = 0; i < height; i++) { + memcpy(fr2->data[0] + (i + y1) * fr2->linesize[0] + x1, fr->data[0] + i * fr->linesize[0], width); + } + for (j = 1; j < 3; j++) { + for (i = 0; i < height / 2; i++) { + memcpy(fr2->data[j] + (i + y1 / 2) * fr2->linesize[j] + x1 / 2, fr->data[j] + i * fr->linesize[j], width / 2); + } + } + } + if (fr) av_frame_free(&fr); + fr = fr2; + fr2 = NULL; } end: if (re == 1 && fr) av_frame_free(&fr); @@ -113,7 +203,7 @@ end: return re; } -THUMBNAIL_ERROR gen_thumbnail(const char* src, const char* dest, int width, int height, THUMBNAIL_METHOD method) { +THUMBNAIL_ERROR gen_thumbnail(const char* src, const char* dest, int width, int height, THUMBNAIL_METHOD method, ALIGN_METHOD align, int quality) { THUMBNAIL_ERROR re = { THUMBNAIL_OK, 0 }; AVFormatContext* ic = NULL, * oc = NULL; AVStream* is = NULL, * os = NULL; @@ -125,6 +215,10 @@ THUMBNAIL_ERROR gen_thumbnail(const char* src, const char* dest, int width, int re.e = THUMBNAIL_UNKNOWN_METHOD; goto end; } + if (align < ALIGN_LEFT || align > ALIGN_RIGHT) { + re.e = THUMBNAIL_UNKNOWN_ALIGN; + goto end; + } if (!src || !dest) { re.e = THUMBNAIL_NULL_POINTER; goto end; @@ -194,6 +288,9 @@ THUMBNAIL_ERROR gen_thumbnail(const char* src, const char* dest, int width, int occ->sample_aspect_ratio = icc->sample_aspect_ratio; occ->time_base = AV_TIME_BASE_Q; occ->color_range = AVCOL_RANGE_JPEG; + occ->global_quality = quality; + occ->qmax = quality; + occ->qmin = quality; if ((re.fferr = avcodec_open2(occ, output_codec, NULL)) < 0) { av_log(NULL, AV_LOG_ERROR, "Failed to open encoder: %s\n", av_err2str(re.fferr)); re.e = THUMBNAIL_FFMPEG_ERROR; @@ -258,7 +355,7 @@ THUMBNAIL_ERROR gen_thumbnail(const char* src, const char* dest, int width, int re.e = THUMBNAIL_FFMPEG_ERROR; goto end; } - if (thumbnail_convert_frame(&re, ifr, &ofr, occ, method)) { + if (thumbnail_convert_frame(&re, ifr, &ofr, occ, method, align)) { av_packet_unref(&pkt); re.e = THUMBNAIL_FFMPEG_ERROR; goto end; @@ -312,6 +409,10 @@ const char* thumbnail_berror(THUMBNAIL_ERROR_E e) { return "No available encoder."; case THUMBNAIL_UNABLE_SCALE: return "Unable to scale image."; + case THUMBNAIL_UNKNOWN_METHOD: + return "Unknown method."; + case THUMBNAIL_UNKNOWN_ALIGN: + return "Unknown align."; default: return "Unknown error."; } diff --git a/extensions/thumbnail/thumbnail.h b/extensions/thumbnail/thumbnail.h index ba83365..2d6918d 100644 --- a/extensions/thumbnail/thumbnail.h +++ b/extensions/thumbnail/thumbnail.h @@ -14,6 +14,7 @@ typedef enum THUMBNAIL_ERROR_E { THUMBNAIL_OOM, THUMBNAIL_NO_ENCODER, THUMBNAIL_UNABLE_SCALE, + THUMBNAIL_UNKNOWN_ALIGN, } THUMBNAIL_ERROR_E; typedef struct THUMBNAIL_ERROR { @@ -27,5 +28,13 @@ typedef enum THUMBNAIL_METHOD { THUMBNAIL_FILL, } THUMBNAIL_METHOD; -PUBLIC_API THUMBNAIL_ERROR gen_thumbnail(const char* src, const char* dest, int width, int height, THUMBNAIL_METHOD method); +typedef enum ALIGN_METHOD { + ALIGN_LEFT, + ALIGN_TOP = 0, + ALIGN_CENTER, + ALIGN_RIGHT, + ALIGN_BOTTOM = 2, +} ALIGN_METHOD; + +PUBLIC_API THUMBNAIL_ERROR gen_thumbnail(const char* src, const char* dest, int width, int height, THUMBNAIL_METHOD method, ALIGN_METHOD align, int quality); PUBLIC_API void thumbnail_error(THUMBNAIL_ERROR e, char* buf, size_t bufsize); diff --git a/thumbnail/base.ts b/thumbnail/base.ts index b450330..d70a634 100644 --- a/thumbnail/base.ts +++ b/thumbnail/base.ts @@ -9,6 +9,14 @@ export enum ThumbnailGenMethod { Fill, } +export enum ThumbnailAlign { + Left, + Top = 0, + Center, + Right, + Bottom = 2, +} + export type ThumbnailConfig = { width: number; height: number; diff --git a/thumbnail/ffmpeg_api.ts b/thumbnail/ffmpeg_api.ts index 233d178..40680b6 100644 --- a/thumbnail/ffmpeg_api.ts +++ b/thumbnail/ffmpeg_api.ts @@ -1,6 +1,6 @@ /// import { Struct } from "pwn/mod.ts"; -import { ThumbnailGenMethod } from "./base.ts"; +import { ThumbnailAlign, ThumbnailGenMethod } from "./base.ts"; let libSuffix = ""; let libPrefix = "lib"; @@ -27,7 +27,15 @@ const lib = Deno.dlopen( libPath, { "gen_thumbnail": { - "parameters": ["buffer", "buffer", "i32", "i32", "i32"], + "parameters": [ + "buffer", + "buffer", + "i32", + "i32", + "i32", + "i32", + "i32", + ], "result": { struct: ["i32", "i32"] }, "nonblocking": true, }, @@ -53,6 +61,8 @@ export async function gen_thumbnail( width: number, height: number, method: ThumbnailGenMethod, + align: ThumbnailAlign, + quality: number, ) { const t = new TextEncoder(); const ore = await lib.symbols.gen_thumbnail( @@ -61,6 +71,8 @@ export async function gen_thumbnail( width, height, method, + align, + quality, ); const re = _Result.unpack(ore); if (re.e) return get_error(ore);