diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f60b41..4bb7c7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ add_library( libscan/font/font.c libscan/font/font.h third-party/utf8.h - libscan/mobi/scan_mobi.c libscan/mobi/scan_mobi.h) + libscan/mobi/scan_mobi.c libscan/mobi/scan_mobi.h libscan/raw/raw.c libscan/raw/raw.h) set_target_properties(scan PROPERTIES LINKER_LANGUAGE C) set(CMAKE_FIND_LIBRARY_SUFFIXES .a .lib) @@ -45,6 +45,9 @@ find_library(HARFBUZZ_LIB NAMES harfbuzz harfbuzzd) find_library(FREETYPE_LIB NAMES freetype freetyped) find_library(LZO2_LIB NAMES lzo2) +find_library(CMS_LIB NAMES lcms) +find_library(JAS_LIB NAMES jasper) + target_compile_options( scan @@ -146,6 +149,11 @@ target_link_libraries( z ${CMAKE_THREAD_LIBS_INIT} + + raw + -fopenmp + ${CMS_LIB} + ${JAS_LIB} ) target_include_directories( diff --git a/libscan/macros.h b/libscan/macros.h index abef523..600f3ba 100644 --- a/libscan/macros.h +++ b/libscan/macros.h @@ -21,13 +21,19 @@ #define ABS(a) (((a) < 0) ? -(a) : (a)) #define APPEND_STR_META(doc, keyname, value) \ - meta_line_t *meta_str = malloc(sizeof(meta_line_t) + strlen(value)); \ + {meta_line_t *meta_str = malloc(sizeof(meta_line_t) + strlen(value)); \ meta_str->key = keyname; \ strcpy(meta_str->str_val, value); \ - APPEND_META(doc, meta_str) + APPEND_META(doc, meta_str)} + +#define APPEND_INT_META(doc, keyname, value) \ + {meta_line_t *meta_int = malloc(sizeof(meta_line_t)); \ + meta_int->key = keyname; \ + meta_int->int_val = value; \ + APPEND_META(doc, meta_int)} #define APPEND_TN_META(doc, width, height) \ - meta_line_t *meta_str = malloc(sizeof(meta_line_t) + 4 + 1 + 4); \ + {meta_line_t *meta_str = malloc(sizeof(meta_line_t) + 4 + 1 + 4); \ meta_str->key = MetaThumbnail; \ sprintf(meta_str->str_val, "%04d,%04d", width, height); \ - APPEND_META(doc, meta_str) + APPEND_META(doc, meta_str)} diff --git a/libscan/media/media.c b/libscan/media/media.c index 1de979b..dcc3ef9 100644 --- a/libscan/media/media.c +++ b/libscan/media/media.c @@ -65,7 +65,7 @@ AVFrame *scale_frame(const AVCodecContext *decoder, const AVFrame *frame, int si struct SwsContext *sws_ctx = sws_getContext( decoder->width, decoder->height, decoder->pix_fmt, dstW, dstH, AV_PIX_FMT_YUVJ420P, - SWS_FAST_BILINEAR, 0, 0, 0 + SIST_SWS_ALGO, 0, 0, 0 ); int dst_buf_len = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, dstW, dstH, 1); diff --git a/libscan/raw/raw.c b/libscan/raw/raw.c new file mode 100644 index 0000000..63c5713 --- /dev/null +++ b/libscan/raw/raw.c @@ -0,0 +1,186 @@ +#include "raw.h" +#include + +#include +#include + +#include "libswscale/swscale.h" +#include "libswresample/swresample.h" +#include "libavcodec/avcodec.h" +#include "libavutil/imgutils.h" + +#include + + +__always_inline +static AVCodecContext *alloc_jpeg_encoder(scan_raw_ctx_t *ctx, int dstW, int dstH, float qscale) { + + AVCodec *jpeg_codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG); + AVCodecContext *jpeg = avcodec_alloc_context3(jpeg_codec); + jpeg->width = dstW; + jpeg->height = dstH; + jpeg->time_base.den = 1000000; + jpeg->time_base.num = 1; + jpeg->i_quant_factor = qscale; + + jpeg->pix_fmt = AV_PIX_FMT_YUVJ420P; + int ret = avcodec_open2(jpeg, jpeg_codec, NULL); + + if (ret != 0) { + CTX_LOG_WARNINGF("raw.c", "Could not open jpeg encoder: %s!\n", av_err2str(ret)) + return NULL; + } + + return jpeg; +} + +#define MIN_SIZE 32 + +void parse_raw(scan_raw_ctx_t *ctx, vfile_t *f, document_t *doc) { + libraw_data_t *libraw_lib = libraw_init(0); + + if (!libraw_lib) { + CTX_LOG_ERROR("raw.c", "Cannot create libraw handle") + return; + } + + size_t buf_len = 0; + void *buf = read_all(f, &buf_len); + + int ret = libraw_open_buffer(libraw_lib, buf, buf_len); + if (ret != 0) { + CTX_LOG_ERROR(f->filepath, "Could not open raw file") + free(buf); + return; + } + ret = libraw_unpack(libraw_lib); + if (ret != 0) { + CTX_LOG_ERROR(f->filepath, "Could not unpack raw file") + free(buf); + libraw_close(libraw_lib); + return; + } + + libraw_dcraw_process(libraw_lib); + + if (*libraw_lib->idata.model != '\0') { + APPEND_STR_META(doc, MetaExifModel, libraw_lib->idata.model) + } + if (*libraw_lib->idata.make != '\0') { + APPEND_STR_META(doc, MetaExifMake, libraw_lib->idata.make) + } + if (*libraw_lib->idata.software != '\0') { + APPEND_STR_META(doc, MetaExifSoftware, libraw_lib->idata.software) + } + APPEND_INT_META(doc, MetaWidth, libraw_lib->sizes.width) + APPEND_INT_META(doc, MetaHeight, libraw_lib->sizes.height) + char tmp[1024]; + snprintf(tmp, sizeof(tmp), "%g", libraw_lib->other.iso_speed); + APPEND_STR_META(doc, MetaExifIsoSpeedRatings, tmp) + + if (*libraw_lib->other.desc != '\0') { + APPEND_STR_META(doc, MetaContent, libraw_lib->other.desc) + } + if (*libraw_lib->other.artist != '\0') { + APPEND_STR_META(doc, MetaArtist, libraw_lib->other.artist) + } + + struct tm *time = localtime(&libraw_lib->other.timestamp); + strftime(tmp, sizeof(tmp), "%Y:%m:%d %H:%M:%S", time); + APPEND_STR_META(doc, MetaExifDateTime, tmp) + + snprintf(tmp, sizeof(tmp), "%.1f", libraw_lib->other.focal_len); + APPEND_STR_META(doc, MetaExifFocalLength, tmp) + + snprintf(tmp, sizeof(tmp), "%.1f", libraw_lib->other.aperture); + APPEND_STR_META(doc, MetaExifFNumber, tmp) + + APPEND_STR_META(doc, MetaMediaVideoCodec, "raw") + + if (ctx->tn_size <= 0) { + free(buf); + libraw_close(libraw_lib); + return; + } + + int errc = 0; + libraw_processed_image_t *img = libraw_dcraw_make_mem_image(libraw_lib, &errc); + if (errc != 0) { + free(buf); + libraw_dcraw_clear_mem(img); + libraw_close(libraw_lib); + return; + } + + int dstW; + int dstH; + + if (img->width <= ctx->tn_size && img->height <= ctx->tn_size) { + dstW = img->width; + dstH = img->height; + } else { + double ratio = (double) img->width / img->height; + if (img->width > img->height) { + dstW = ctx->tn_size; + dstH = (int) (ctx->tn_size / ratio); + } else { + dstW = (int) (ctx->tn_size * ratio); + dstH = ctx->tn_size; + } + } + + if (dstW <= MIN_SIZE || dstH <= MIN_SIZE) { + free(buf); + libraw_dcraw_clear_mem(img); + libraw_close(libraw_lib); + return; + } + + AVFrame *scaled_frame = av_frame_alloc(); + + struct SwsContext *sws_ctx= sws_getContext( + img->width, img->height, AV_PIX_FMT_RGB24, + dstW, dstH, AV_PIX_FMT_YUVJ420P, + SIST_SWS_ALGO, 0, 0, 0 + ); + + int dst_buf_len = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, dstW, dstH, 1); + uint8_t *dst_buf = (uint8_t *) av_malloc(dst_buf_len); + + av_image_fill_arrays(scaled_frame->data, scaled_frame->linesize, dst_buf, AV_PIX_FMT_YUV420P, dstW, dstH, 1); + + const uint8_t *inData[1] = {img->data}; + int inLinesize[1] = {3 * img->width}; + + sws_scale(sws_ctx, + inData, inLinesize, + 0, img->height, + scaled_frame->data, scaled_frame->linesize + ); + + scaled_frame->width = dstW; + scaled_frame->height = dstH; + scaled_frame->format = AV_PIX_FMT_YUV420P; + + sws_freeContext(sws_ctx); + + AVCodecContext *jpeg_encoder = alloc_jpeg_encoder(ctx, scaled_frame->width, scaled_frame->height, 1.0f); + avcodec_send_frame(jpeg_encoder, scaled_frame); + + AVPacket jpeg_packet; + av_init_packet(&jpeg_packet); + avcodec_receive_packet(jpeg_encoder, &jpeg_packet); + + APPEND_TN_META(doc, scaled_frame->width, scaled_frame->height) + ctx->store((char *) doc->uuid, sizeof(doc->uuid), (char *) jpeg_packet.data, jpeg_packet.size); + + av_packet_unref(&jpeg_packet); + av_free(*scaled_frame->data); + av_frame_free(&scaled_frame); + avcodec_free_context(&jpeg_encoder); + + libraw_dcraw_clear_mem(img); + libraw_close(libraw_lib); + + free(buf); +} diff --git a/libscan/raw/raw.h b/libscan/raw/raw.h new file mode 100644 index 0000000..08202e3 --- /dev/null +++ b/libscan/raw/raw.h @@ -0,0 +1,17 @@ +#ifndef SIST2_RAW_H +#define SIST2_RAW_H + +#include "../scan.h" + +typedef struct { + int tn_size; + float tn_qscale; + + log_callback_t log; + logf_callback_t logf; + store_callback_t store; +} scan_raw_ctx_t; + +void parse_raw(scan_raw_ctx_t *ctx, vfile_t *f, document_t *doc); + +#endif //SIST2_RAW_H diff --git a/libscan/scan.h b/libscan/scan.h index 4251004..c05a334 100644 --- a/libscan/scan.h +++ b/libscan/scan.h @@ -7,6 +7,8 @@ #include "macros.h" +#define SIST_SWS_ALGO SWS_LANCZOS + #define META_INT_MASK 0x80 #define META_STR_MASK 0x40 #define META_LONG_MASK 0x20 diff --git a/test/main.cpp b/test/main.cpp index 4eee5a3..9c8e546 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -8,6 +8,7 @@ extern "C" { #include "../libscan/media/media.h" #include "../libscan/ooxml/ooxml.h" #include "../libscan/mobi/scan_mobi.h" +#include "../libscan/raw/raw.h" #include } @@ -25,6 +26,8 @@ static scan_ooxml_ctx_t ooxml_500_ctx; static scan_mobi_ctx_t mobi_500_ctx; +static scan_raw_ctx_t raw_ctx; + document_t LastSubDoc; @@ -455,6 +458,92 @@ TEST(Arc, Utf8) { cleanup(&doc, &f); } +/* RAW */ +TEST(RAW, Panasonic) { + vfile_t f; + document_t doc; + load_doc_file("libscan-test-files/test_files/raw/Panasonic.RW2", &f, &doc); + + parse_raw(&raw_ctx, &f, &doc); + + ASSERT_STREQ(get_meta(&doc, MetaMediaVideoCodec)->str_val, "raw"); + ASSERT_STREQ(get_meta(&doc, MetaExifModel)->str_val, "DMC-GX8"); + ASSERT_STREQ(get_meta(&doc, MetaExifMake)->str_val, "Panasonic"); + ASSERT_STREQ(get_meta(&doc, MetaExifIsoSpeedRatings)->str_val, "640"); + ASSERT_STREQ(get_meta(&doc, MetaExifDateTime)->str_val, "2020:07:20 10:00:34"); + ASSERT_STREQ(get_meta(&doc, MetaExifFocalLength)->str_val, "20.0"); + ASSERT_STREQ(get_meta(&doc, MetaExifFNumber)->str_val, "2.0"); + ASSERT_EQ(get_meta(&doc, MetaWidth)->int_val, 5200); + ASSERT_EQ(get_meta(&doc, MetaHeight)->int_val, 3904); + + + cleanup(&doc, &f); +} + +TEST(RAW, Nikon) { + vfile_t f; + document_t doc; + load_doc_file("libscan-test-files/test_files/raw/Nikon.NEF", &f, &doc); + + parse_raw(&raw_ctx, &f, &doc); + + ASSERT_STREQ(get_meta(&doc, MetaMediaVideoCodec)->str_val, "raw"); + ASSERT_STREQ(get_meta(&doc, MetaExifModel)->str_val, "D750"); + ASSERT_STREQ(get_meta(&doc, MetaExifMake)->str_val, "Nikon"); + ASSERT_EQ(get_meta(&doc, MetaWidth)->int_val, 6032); + ASSERT_EQ(get_meta(&doc, MetaHeight)->int_val, 4032); + + cleanup(&doc, &f); +} + +TEST(RAW, Sony) { + vfile_t f; + document_t doc; + load_doc_file("libscan-test-files/test_files/raw/Sony.ARW", &f, &doc); + + parse_raw(&raw_ctx, &f, &doc); + + ASSERT_STREQ(get_meta(&doc, MetaMediaVideoCodec)->str_val, "raw"); + ASSERT_STREQ(get_meta(&doc, MetaExifModel)->str_val, "ILCE-7RM3"); + ASSERT_STREQ(get_meta(&doc, MetaExifMake)->str_val, "Sony"); + ASSERT_EQ(get_meta(&doc, MetaWidth)->int_val, 7968); + ASSERT_EQ(get_meta(&doc, MetaHeight)->int_val, 5320); + + cleanup(&doc, &f); +} + +TEST(RAW, Olympus) { + vfile_t f; + document_t doc; + load_doc_file("libscan-test-files/test_files/raw/Olympus.ORF", &f, &doc); + + parse_raw(&raw_ctx, &f, &doc); + + ASSERT_STREQ(get_meta(&doc, MetaMediaVideoCodec)->str_val, "raw"); + ASSERT_STREQ(get_meta(&doc, MetaExifModel)->str_val, "E-M5MarkII"); + ASSERT_STREQ(get_meta(&doc, MetaExifMake)->str_val, "Olympus"); + ASSERT_EQ(get_meta(&doc, MetaWidth)->int_val, 4640); + ASSERT_EQ(get_meta(&doc, MetaHeight)->int_val, 3472); + + cleanup(&doc, &f); +} + +TEST(RAW, Fuji) { + vfile_t f; + document_t doc; + load_doc_file("libscan-test-files/test_files/raw/Fuji.RAF", &f, &doc); + + parse_raw(&raw_ctx, &f, &doc); + + ASSERT_STREQ(get_meta(&doc, MetaMediaVideoCodec)->str_val, "raw"); + ASSERT_STREQ(get_meta(&doc, MetaExifModel)->str_val, "X-T2"); + ASSERT_STREQ(get_meta(&doc, MetaExifMake)->str_val, "Fujifilm"); + ASSERT_EQ(get_meta(&doc, MetaWidth)->int_val, 6032); + ASSERT_EQ(get_meta(&doc, MetaHeight)->int_val, 4028); + + cleanup(&doc, &f); +} + int main(int argc, char **argv) { setlocale(LC_ALL, ""); @@ -500,6 +589,12 @@ int main(int argc, char **argv) { mobi_500_ctx.log = noop_log; mobi_500_ctx.logf = noop_logf; + raw_ctx.log = noop_log; + raw_ctx.logf = noop_logf; + raw_ctx.store = noop_store; + raw_ctx.tn_size = 500; + raw_ctx.tn_qscale = 5.0; + av_log_set_level(AV_LOG_QUIET); ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS();