У меня есть коллекция файлов 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(¶ms, 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, ¶ms);
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: Мне нужно написать это предложение, потому что мой пост в основном состоит из кода...
@UlrichEckhardt Теперь с постом все должно быть в порядке.
Как вы думаете, какую ценность имеет sizeof(*dst_file)
с void *dst_file
?
Разве это не должно быть уловлено путем включения предупреждений?
@mch Перед этим программа вылетает.
@mch GCC допускает sizeof(void)
(= 1) в качестве расширения, возможно, потому, что он позволяет выполнять арифметические действия с void *
.
@IanAbbott Программа вылетает до этого момента.
strcat(strtok(dst_name, "."), ".JPG");
крайне небезопасно. Итак, graph
NULL? tmp
НУЛЬ? gcc
zimg написан на C++, для компиляции необходимо использовать g++
. Используйте g++
. Скорее всего, некоторые статические данные Zimg не инициализированы. | Вы должны проверить ошибки каждого распределения. dst_jpeg_buf
НУЛЬ?
@KamilCuk Готово. Я не думаю, что g++
больше gcc
будет иметь какое-либо значение, поскольку я использую zimg C API
, но как хотите.
Вы используете 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 Я не знаю, правильно ли я перевел код; Лично я не знаю C++, поэтому использую zimg C API.
zimg C API не будет существовать, если программа ДОЛЖНА быть написана на C++;) В репозитории zimg есть пример использования C API на C: github.com/sekrit-twc/zimg/blob/master/doc/ пример/…
Вы не проверяете возврат вызываемых функций zimg. Сначала сделайте это, как показано в примере в комментарии выше.
@trexxet Недостаточно использовать zimg_get_last_error
при каждом вызове API zimg C?
привет @trexxet пример в исходном коде zimq компилируется с помощью gcc в объектный файл, а затем связывается с g++. Представленный здесь код скомпилирован и связан с gcc. Кроме того, в zimq есть ошибка, из-за которой функция смешивания C и C++ main()
должна быть скомпилирована компилятором C++. См. isocpp.org/wiki/faq/mixing-c-and-cpp#overview-mixing-langs .
@trexxet Может быть, память не выровнена после того, как я прочитал JPEG в буфер? Как выровнять буфер памяти, в котором уже хранятся байты?
Does not suffice to use zimg_get_last_error
«Программисты смотрят в обе стороны на улице с односторонним движением».
@KamilCuk О какой ошибке вы говорите? Можете ли вы уточнить, пожалуйста? Спасибо
Во-первых, на основе примера 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(¶ms, 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, ¶ms);
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;
}
Здесь есть несколько версий (разных?) вашего кода?!? Пожалуйста, убедитесь, что вы включили минимально воспроизводимый пример и результат, который он генерирует.