Libzimg: AddressSanitizer: SEGV

У меня есть коллекция файлов JPEG, которые я хотел бы уменьшить до определенного размера с помощью библиотеки zimg (она же z.lib):

Это мой фрагмент кода:

#include <cstdio> // fopen, fseek, ftell, fread, fwrite
#include <cstdint> // uint8_t
#include <cstdlib> // aligned_alloc, free
#include <cstring> // strlen, strtok, strcat
#include <cerrno> // errno

#include <jpeglib.h> // jpeg compression/decompression routines

#include <zimg.h> // colorspace and scaling routines

static void print_zimg_error(const char *function_name)
{
    char err_msg[1024];
    int err_code = zimg_get_last_error(err_msg, sizeof(err_msg));
    if (err_code != ZIMG_ERROR_SUCCESS)
        fprintf(stderr, "%s(): %s\n", function_name, err_msg);

    zimg_clear_last_error();

    return;
}

static int width_to_stride(int w)
{
    return ((w + 31) & ~31);
}

int main(int argc, char **argv)
{
    if (argc != 2) {
        fprintf(stderr, "usage: %s <filename>.JPG\n", argv[0]);
        return 1;
    }

    const char *src_name = argv[1];
    char *src_name_2 = strdup(src_name);
    const char *delim = "/";
    char *tok;
    char *dst_name;

    strtok(src_name_2, delim);
    while ((tok = strtok(NULL, delim)) != NULL)
        dst_name = tok;

    dst_name = strcat(strtok(dst_name, "."), ".JPG");

    // Define input sizes
    int src_width = 5152;
    int src_height = 3864;
    int src_stride = width_to_stride(src_width) + (width_to_stride(src_width / 2) * 2);
    int src_pixel_count = (src_stride * src_height) + (src_stride * (src_height / 2)) + (src_stride * (src_height / 2));

    // Define output sizes
    int dst_width = 2560;
    int dst_height = 1400;
    int dst_stride = width_to_stride(dst_width) + (width_to_stride(dst_width / 2) * 2);
    int dst_pixel_count = (dst_stride * dst_height) + (dst_stride * (dst_height / 2)) + (dst_stride * (dst_height / 2));

    uint8_t *src_jpeg_buf = (uint8_t *) aligned_alloc(32, ((src_pixel_count) + 32 - 1) & ~(32 - 1));
    if (src_jpeg_buf == NULL) {
        fprintf(stderr, "aligned_alloc(): src_jpeg_buf is NULL\n");
        return 1;
    }
    uint8_t *dst_jpeg_buf = (uint8_t *) aligned_alloc(32, ((dst_pixel_count) + 32 - 1) & ~(32 - 1));
    if (dst_jpeg_buf == NULL) {
        fprintf(stderr, "aligned_alloc(): dst_jpeg_buf is NULL\n");
        return 1;
    }

    FILE *src_file = fopen(src_name, "rb");
    if (src_file == NULL) {
        perror("fopen()");
        return 1;
    }

    struct jpeg_decompress_struct jpeg_dec;
    struct jpeg_error_mgr jpeg_err;

    jpeg_dec.err = jpeg_std_error(&jpeg_err);
    jpeg_create_decompress(&jpeg_dec);

    jpeg_stdio_src(&jpeg_dec, src_file);

    jpeg_read_header(&jpeg_dec, TRUE);

    jpeg_start_decompress(&jpeg_dec);

    int row_stride = jpeg_dec.output_width * jpeg_dec.output_components;
    JSAMPARRAY scanline_buffer = (*jpeg_dec.mem->alloc_sarray)((j_common_ptr) &jpeg_dec, JPOOL_IMAGE, row_stride, 1);

    while (jpeg_dec.output_scanline < jpeg_dec.output_height) {
        jpeg_read_scanlines(&jpeg_dec, scanline_buffer, 1);
        memcpy(src_jpeg_buf + (jpeg_dec.output_scanline - 1) * row_stride, scanline_buffer[0], row_stride);
    }

    jpeg_finish_decompress(&jpeg_dec);
    jpeg_destroy_decompress(&jpeg_dec);

    fclose(src_file);

    // Initialize zimg structures to defaults
    zimg_filter_graph *graph = 0;
    zimg_image_buffer_const src_buf = { ZIMG_API_VERSION }; 
    zimg_image_buffer dst_buf = { ZIMG_API_VERSION }; 
    zimg_image_format src_format;
    zimg_image_format dst_format;
    zimg_graph_builder_params params;
    size_t tmp_size;
    void *tmp = 0;
    
    zimg_image_format_default(&src_format, ZIMG_API_VERSION); 
    print_zimg_error("zimg_image_format_default");

    zimg_image_format_default(&dst_format, ZIMG_API_VERSION); 
    print_zimg_error("zimg_image_format_default");

    zimg_graph_builder_params_default(&params, ZIMG_API_VERSION);
    params.resample_filter = ZIMG_RESIZE_BICUBIC;
    params.filter_param_a = -0.5;
    params.filter_param_b = 0.25;
    params.resample_filter_uv = ZIMG_RESIZE_BICUBIC;
    params.filter_param_a_uv = -0.5;
    params.filter_param_b_uv = 0.25;

    src_format.width = src_width;
    src_format.height = src_height;
    src_format.pixel_type = ZIMG_PIXEL_BYTE;

    src_format.subsample_w = 1;
    src_format.subsample_h = 0;

    src_format.color_family = ZIMG_COLOR_YUV;

    dst_format.width = dst_width;
    dst_format.height = dst_height;
    dst_format.pixel_type = ZIMG_PIXEL_BYTE;

    dst_format.subsample_w = 1;
    dst_format.subsample_h = 1;

    dst_format.color_family = ZIMG_COLOR_YUV;

    graph = zimg_filter_graph_build(&src_format, &dst_format, &params);
    if (graph == NULL) {
        fprintf(stderr, "zimg_filter_graph_build(): graph is NULL\n");
        return 1;
    }
    print_zimg_error("zimg_filter_graph_build");

    zimg_filter_graph_get_tmp_size(graph, &tmp_size);
    print_zimg_error("zimg_filter_graph_get_tmp_size");

    tmp = aligned_alloc(32, tmp_size);
    if (tmp == NULL) {
        fprintf(stderr, "aligned_alloc(): tmp is NULL\n");
        return 1;
    }

    src_buf.plane[0].data = src_jpeg_buf;
    src_buf.plane[0].stride = src_stride;
    src_buf.plane[0].mask = ZIMG_BUFFER_MAX;

    src_buf.plane[0].data = dst_jpeg_buf;
    src_buf.plane[0].stride = dst_stride;
    src_buf.plane[0].mask = ZIMG_BUFFER_MAX;

    zimg_filter_graph_process(graph, &src_buf, &dst_buf, tmp, 0, 0, 0, 0);
    print_zimg_error("zimg_filter_graph_process");

    zimg_filter_graph_free(graph);
    print_zimg_error("zimg_filter_graph_free");

    free(tmp);

    FILE *dst_file = fopen(dst_name, "wb");
    fwrite(dst_jpeg_buf, dst_pixel_count, 1, dst_file);
    fclose(dst_file);

    free(src_jpeg_buf);
    free(dst_jpeg_buf);

    return 0;
}

но я получаю эту ошибку серьезности от терминала:

==136680==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x7e3914dcd808 bp 0x7fff56f8efa0 sp 0x7fff56f8de00 T0)
==136680==The signal is caused by a WRITE memory access.
==136680==Hint: address points to the zero page.
    #0 0x7e3914dcd808 in _mm_storeu_si128(long long __vector(2)*, long long __vector(2)) /usr/lib/gcc/x86_64-linux-gnu/11/include/emmintrin.h:739
    #1 0x7e3914dcd808 in store16 src/zimg/depth/x86/dither_avx2.cpp:66
    #2 0x7e3914dcd808 in ordered_dither_avx2_impl<zimg::depth::(anonymous namespace)::LoadU16, zimg::depth::(anonymous namespace)::StoreU8> src/zimg/depth/x86/dither_avx2.cpp:149
    #3 0x7e3914dcd808 in zimg::depth::ordered_dither_w2b_avx2(float const*, unsigned int, unsigned int, void const*, void*, float, float, unsigned int, unsigned int, unsigned int) src/zimg/depth/x86/dither_avx2.cpp:178
    #4 0x7e3914c7f666 in process src/zimg/depth/dither.cpp:281
    #5 0x7e3914c4d544 in process graphengine/graphengine/node.cpp:635
    #6 0x7e3914c3cdde  (/usr/local/lib/libzimg.so.2+0x3cdde)
    #7 0x7e3914c3e67d  (/usr/local/lib/libzimg.so.2+0x3e67d)
    #8 0x7e3914c39566 in graphengine::zimg::GraphImpl::run(graphengine::Graph::Endpoint const*, void*) const graphengine/graphengine/graph.cpp:877
    #9 0x7e3914c8706d in zimg::graph::FilterGraph::process(std::array<graphengine::BufferDescriptor, 4ul> const&, std::array<graphengine::BufferDescriptor, 4ul> const&, void*, int (*)(void*, unsigned int, unsigned int, unsigned int), void*, int (*)(void*, unsigned int, unsigned int, unsigned int), void*) const src/zimg/graph/filtergraph.cpp:85
    #10 0x7e3914c536d1 in zimg_filter_graph_process src/zimg/api/zimg.cpp:645
    #11 0x5b5fabb4b8e6 in main /home/gianluca/Informatica/Programmazione/C/aws-lambda-cpp-samples/aws-lambda-jpeg-zimg/test.c:167
    #12 0x7e3914429d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #13 0x7e3914429e3f in __libc_start_main_impl ../csu/libc-start.c:392
    #14 0x5b5fabb4a544 in _start (/home/gianluca/Informatica/Programmazione/C/aws-lambda-cpp-samples/aws-lambda-jpeg-zimg/test+0x2544)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /usr/lib/gcc/x86_64-linux-gnu/11/include/emmintrin.h:739 in _mm_storeu_si128(long long __vector(2)*, long long __vector(2))
==136680==ABORTING

Фрагмент кода был скомпилирован с помощью:

g++ -Wall -Werror -fsanitize=address -g -std=gnu++11 -o test test.cpp $(pkg-config --cflags --libs libjpeg zimg)

и беги с ./test <filename>

Любое предложение ?

Джанлука

PS: Мне нужно написать это предложение, потому что мой пост в основном состоит из кода...

Здесь есть несколько версий (разных?) вашего кода?!? Пожалуйста, убедитесь, что вы включили минимально воспроизводимый пример и результат, который он генерирует.

Ulrich Eckhardt 25.06.2024 15:49

@UlrichEckhardt Теперь с постом все должно быть в порядке.

aculnaig 25.06.2024 15:52

Как вы думаете, какую ценность имеет sizeof(*dst_file) с void *dst_file?

mch 25.06.2024 16:05

Разве это не должно быть уловлено путем включения предупреждений?

Ulrich Eckhardt 25.06.2024 16:11

@mch Перед этим программа вылетает.

aculnaig 25.06.2024 16:28

@mch GCC допускает sizeof(void) (= 1) в качестве расширения, возможно, потому, что он позволяет выполнять арифметические действия с void *.

Ian Abbott 25.06.2024 16:38

@IanAbbott Программа вылетает до этого момента.

aculnaig 28.06.2024 09:50
strcat(strtok(dst_name, "."), ".JPG"); крайне небезопасно. Итак, graph NULL? tmp НУЛЬ? gcc zimg написан на C++, для компиляции необходимо использовать g++. Используйте g++. Скорее всего, некоторые статические данные Zimg не инициализированы. | Вы должны проверить ошибки каждого распределения. dst_jpeg_buf НУЛЬ?
KamilCuk 01.07.2024 15:11

@KamilCuk Готово. Я не думаю, что g++ больше gcc будет иметь какое-либо значение, поскольку я использую zimg C API, но как хотите.

aculnaig 01.07.2024 15:20

Вы используете zimg C API, который перенаправляет C++ API. Если вы связываетесь с библиотеками C++, основная функция должна быть скомпилирована с C++ для запуска статических конструкторов C++. Даже в трассировке стека вы можете увидеть zimg::depth::ordered_d выполненные шаблоны C++. Рассмотрите возможность рефакторинга main() из файла C в файл C++. Судя по трассировке стека, я предполагаю, что аргумент dst для ordered_dither_w2b_avx2 имеет значение NULL`, но я потерял, откуда он взялся, я подозреваю, что это какой-то аргумент назначения. Подключили ли вы отладчик и просмотрели трассировку стека, чтобы проверить ошибку? Gdb обнаружит SIGSEGV.

KamilCuk 01.07.2024 15:22

@KamilCuk Я не знаю, правильно ли я перевел код; Лично я не знаю C++, поэтому использую zimg C API.

aculnaig 01.07.2024 15:32

zimg C API не будет существовать, если программа ДОЛЖНА быть написана на C++;) В репозитории zimg есть пример использования C API на C: github.com/sekrit-twc/zimg/blob/master/doc/ пример/…

trexxet 01.07.2024 15:32

Вы не проверяете возврат вызываемых функций zimg. Сначала сделайте это, как показано в примере в комментарии выше.

trexxet 01.07.2024 15:40

@trexxet Недостаточно использовать zimg_get_last_error при каждом вызове API zimg C?

aculnaig 01.07.2024 15:42

привет @trexxet пример в исходном коде zimq компилируется с помощью gcc в объектный файл, а затем связывается с g++. Представленный здесь код скомпилирован и связан с gcc. Кроме того, в zimq есть ошибка, из-за которой функция смешивания C и C++ main() должна быть скомпилирована компилятором C++. См. isocpp.org/wiki/faq/mixing-c-and-cpp#overview-mixing-langs .

KamilCuk 01.07.2024 15:45

@trexxet Может быть, память не выровнена после того, как я прочитал JPEG в буфер? Как выровнять буфер памяти, в котором уже хранятся байты?

aculnaig 01.07.2024 15:45
Does not suffice to use zimg_get_last_error «Программисты смотрят в обе стороны на улице с односторонним движением».
KamilCuk 01.07.2024 15:46

@KamilCuk О какой ошибке вы говорите? Можете ли вы уточнить, пожалуйста? Спасибо

aculnaig 01.07.2024 15:50
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
18
172
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Во-первых, на основе примера C и make-файла для примеры в zimg можно писать C как обычно (#include <stdio.h>) и скомпилируйте с помощью gcc; вам просто нужно обязательно использовать ссылку g++, например:

CFLAGS = -Wall -Werror -g
test: test.o
    g++ test.o -pthread $$(pkg-config --libs libjpeg zimg) -o test
test.o: test.c
    gcc -std=c11 $(CFLAGS) -pthread $$(pkg-config --cflags libjpeg zimg) -c test.c

Основная проблема с кодом заключается в том, что чтение и копирование строк развертки Возврат через libjpeg создает в памяти следующее расположение пикселей:

scanline 1
R G B R G B R G B ...
scanline 2
R G B R G B R G B ...
scanline 3
R G B R G B R G B ...
...

С другой стороны, zimg рассчитывает работать с каждым каналом отдельно, то есть:

buf.plane[0]:
R R R R R R R R ...
R R R R R R R R ...
...
buf.plane[1]:
G G G G G G G G ...
G G G G G G G G ...
...
buf.plane[2]:
B B B B B B B B ...
B B B B B B B B ...
...

Где каждая строка канала выровнена по 32 байта. Таким образом, вместо того, чтобы копировать каждый строки сканирования при чтении JPEG, вам следует извлечь каждый компонент пикселя в соответствующая плоскость Зимга.

Кроме того, вы распаковали входной JPEG и записали пиксели как есть в файл. выходной файл. Чтобы записать JPEG, вам нужно использовать libjpeg для повторного сжатия. После масштабируя с помощью zimg, пиксели можно снова собрать в строки сканирования RGB для дальнейшего использования. с компрессором.

Собираем все это вместе:

#include <errno.h>
#include <setjmp.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <jpeglib.h>
#include <zimg.h>

static JDIMENSION dimension_arg(const char *arg) {
    long value;
    char *end;
    errno = 0;
    value = strtol(arg, &end, 10);
    if (errno != 0 || end == arg || *end != '\0' || value < 1 || value > JPEG_MAX_DIMENSION) {
        (void)fprintf(
            stderr, "Invalid JPEG dimension (range [1-%ld]): %s\n", JPEG_MAX_DIMENSION, arg
        );
        return 0;
    }
    return value;
}

struct image {
    struct zimg_image_format format;
    uint8_t *data;
    uint8_t *plane[3];
    unsigned int stride;
};

#define PAD32(x) (((x) + 0x1f) & ~0x1f)

static int image_init(struct image *image, unsigned int width, unsigned int height) {
    zimg_image_format_default(&image->format, ZIMG_API_VERSION);
    image->format.width = width;
    image->format.height = height;
    image->format.pixel_type = ZIMG_PIXEL_BYTE;
    image->format.color_family = ZIMG_COLOR_RGB;
    image->format.pixel_range = ZIMG_RANGE_FULL;

    image->stride = PAD32(width);
    size_t plane_size = (size_t)image->stride * height;
    image->data = aligned_alloc(32, plane_size * 3);
    if (image->data == NULL) {
        (void)fputs("Failed to allocate image memory\n", stderr);
        return 0;
    }
    for (int i = 0; i < 3; ++i)
        image->plane[i] = image->data + plane_size * i;

    return 1;
}

static void image_free(struct image *image) {
    free(image->data);
}

static void handle_jpeg_error(j_common_ptr cinfo) {
    (*cinfo->err->output_message)(cinfo);
    jmp_buf *env = cinfo->client_data;
    jpeg_destroy(cinfo);
    longjmp(*env, 1);
}

static int read_jpeg(const char *path, struct image *image) {
    FILE *file = fopen(path, "rb");
    if (file == NULL) {
        (void)fprintf(stderr, "Failed to open input JPEG %s: %s\n", path, strerror(errno));
        return 0;
    }

    struct jpeg_decompress_struct cinfo;
    struct jpeg_error_mgr jerr;

    cinfo.err = jpeg_std_error(&jerr);
    jerr.error_exit = handle_jpeg_error;

    volatile int allocated_image = 0;
    jmp_buf env;
    cinfo.client_data = &env;
    if (setjmp(env)) {
        // Fatal libjpeg error; we've already destroyed cinfo.
        (void)fclose(file);
        if (allocated_image)
            image_free(image);
        return 0;
    }

    jpeg_create_decompress(&cinfo);
    // We are using a non-suspending data source (stdio), so the return value of read_header,
    // start_decompress, and finish_decompress can be ignored.
    jpeg_stdio_src(&cinfo, file);

    (void)jpeg_read_header(&cinfo, 1);
#ifdef DEBUG
    fprintf(
        stderr, "Input JPEG: dim=%ux%u, space=%d, components=%d\n",
        cinfo.image_width, cinfo.image_height, cinfo.jpeg_color_space, cinfo.num_components
    );
#endif
    // Always decompress to RGB. This means we will have 3 output components.
    cinfo.out_color_space = JCS_RGB;

    // Start decompress: fills out cinfo.out*.
    (void)jpeg_start_decompress(&cinfo);

    if (!image_init(image, cinfo.output_width, cinfo.output_height))
        goto err_destroy_jpeg;
    allocated_image = 1;

    // libjpeg-managed scanline buffer, freed on cinfo destroy. Assume cinfo.rec_outbuf_height is
    // just 1 line since we use the default decompression.
    JDIMENSION scan_size = cinfo.output_width * sizeof(JSAMPLE) * 3;
    JSAMPARRAY scan_buf =
        (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, scan_size, 1);

    size_t line_start = 0;
    while (cinfo.output_scanline < cinfo.output_height) {
        (void)jpeg_read_scanlines(&cinfo, scan_buf, 1);
        JSAMPROW p = scan_buf[0];
        for (JDIMENSION i = 0; i < cinfo.output_width; ++i) {
            image->plane[0][line_start + i] = p[0];
            image->plane[1][line_start + i] = p[1];
            image->plane[2][line_start + i] = p[2];
            p += 3;
        }
        line_start += image->stride;
    }

    (void)jpeg_finish_decompress(&cinfo);
    jpeg_destroy_decompress(&cinfo);
    (void)fclose(file);
    return 1;

err_destroy_jpeg:
    jpeg_destroy_decompress(&cinfo);
    (void)fclose(file);
    return 0;
}

static int write_jpeg(const char *path, struct image *image) {
    FILE *file = fopen(path, "wb");
    if (file == NULL) {
        (void)fprintf(stderr, "Failed to open path for writing %s: %s\n", path, strerror(errno));
        return 0;
    }

    // See comments in read_jpeg for libjpeg operation details.
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;

    cinfo.err = jpeg_std_error(&jerr);
    jerr.error_exit = handle_jpeg_error;

    jmp_buf env;
    cinfo.client_data = &env;
    if (setjmp(env)) {
        (void)fclose(file);
        return 0;
    }

    jpeg_create_compress(&cinfo);
    jpeg_stdio_dest(&cinfo, file);

    cinfo.image_width = image->format.width;
    cinfo.image_height = image->format.height;
    cinfo.input_components = 3;
    cinfo.in_color_space = JCS_RGB;

    jpeg_set_defaults(&cinfo);
    jpeg_set_quality(&cinfo, 80, 1);

    jpeg_start_compress(&cinfo, 1);

    JDIMENSION scan_size = cinfo.image_width * sizeof(JSAMPLE) * 3;
    JSAMPARRAY scan_buf =
        (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, scan_size, 1);

    size_t line_start = 0;
    while (cinfo.next_scanline < cinfo.image_height) {
        JSAMPROW p = scan_buf[0];
        for (JDIMENSION i = 0; i < cinfo.image_width; ++i) {
            p[0] = image->plane[0][line_start + i];
            p[1] = image->plane[1][line_start + i];
            p[2] = image->plane[2][line_start + i];
            p += 3;
        }
        (void)jpeg_write_scanlines(&cinfo, scan_buf, 1);
        line_start += image->stride;
    }

    jpeg_finish_compress(&cinfo);
    jpeg_destroy_compress(&cinfo);
    if (fclose(file) == EOF) {
        (void)fprintf(stderr, "Failed to finish writing %s: %s\n", path, strerror(errno));
        return 0;
    }

    return 1;
}

static void output_zimg_error(const char *func) {
    char buf[1024];
    enum zimg_error_code_e zerr = zimg_get_last_error(buf, sizeof(buf));
    (void)fprintf(stderr, "%s: error %d: %s\n", func, zerr, buf);
}

static int scale(struct image *in, struct image *out) {
    struct zimg_graph_builder_params params;
    zimg_graph_builder_params_default(&params, ZIMG_API_VERSION);
    params.resample_filter = ZIMG_RESIZE_LANCZOS;
    params.resample_filter_uv = ZIMG_RESIZE_LANCZOS;

    struct zimg_filter_graph *graph = zimg_filter_graph_build(&in->format, &out->format, &params);
    if (graph == NULL) {
        output_zimg_error("zimg_filter_graph_build");
        goto err;
    }

    enum zimg_error_code_e zerr;
    size_t tmp_size;
    zerr = zimg_filter_graph_get_tmp_size(graph, &tmp_size);
    if (zerr != ZIMG_ERROR_SUCCESS) {
        output_zimg_error("zimg_filter_graph_get_tmp_size");
        goto err_free_graph;
    }

    void *tmp = aligned_alloc(32, tmp_size);
    if (tmp == NULL) {
        (void)fputs("Failed to allocate memory for zimg graph processing\n", stderr);
        goto err_free_graph;
    }

    struct zimg_image_buffer_const src;
    struct zimg_image_buffer dst;
    src.version = dst.version = ZIMG_API_VERSION;
    for (int p = 0; p < 3; ++p) {
        src.plane[p].data = in->plane[p];
        src.plane[p].stride = in->stride;
        src.plane[p].mask = ZIMG_BUFFER_MAX;

        dst.plane[p].data = out->plane[p];
        dst.plane[p].stride = out->stride;
        dst.plane[p].mask = ZIMG_BUFFER_MAX;
    }

    zerr = zimg_filter_graph_process(graph, &src, &dst, tmp, NULL, NULL, NULL, NULL);
    if (zerr != ZIMG_ERROR_SUCCESS) {
        output_zimg_error("zimg_filter_graph_process");
        goto err_free_tmp;
    }

    free(tmp);
    zimg_filter_graph_free(graph);
    return 1;

err_free_tmp:
    free(tmp);
err_free_graph:
    zimg_filter_graph_free(graph);
err:
    return 0;
}

int main(int argc, char *argv[]) {
    if (argc != 5) {
        (void)fprintf(stderr, "Usage: %s <in JPEG> <out JPEG> <out width> <out height>\n", argv[0]);
        return 2;
    }

    const char *in_path = argv[1];
    const char *out_path = argv[2];
    JDIMENSION out_width = dimension_arg(argv[3]);
    if (out_width == 0)
        return 2;
    JDIMENSION out_height = dimension_arg(argv[4]);
    if (out_height == 0)
        return 2;

    struct image out;
    if (!image_init(&out, out_width, out_height))
        goto err;

    struct image in;
    if (!read_jpeg(in_path, &in))
        goto err_free_out;

    printf("Scaling %s to %ux%u...\n", in_path, out_width, out_height);
    if (!scale(&in, &out))
        goto err_free_in;

    if (!write_jpeg(out_path, &out))
        goto err_free_in;
    printf("Wrote scaled image to %s\n", out_path);

    image_free(&in);
    image_free(&out);

    return 0;

err_free_in:
    image_free(&in);
err_free_out:
    image_free(&out);
err:
    return 1;
}

Другие вопросы по теме