diff --git a/CMakeLists.txt b/CMakeLists.txt index 0020e80..fe2cbdb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -166,7 +166,9 @@ if(HAIO_TEST) include(CTest) set(tests "test_get_format_from_extension,src/Common_String/get_format_from_ext.c" + "test_get_format_from_magic,src/Common_String/get_format_from_magic.c" "test_get_codec_workers_from_formats,src/Backend_Workers/codecs.c" + "test_convert_tokenize_args,src/Frontend_Convert/tokenize.c,src/Frontend_Convert/format.c,src/Common_String/get_ext_from_str.c,src/Common_String/get_format_from_ext.c,src/Common_String/get_format_from_magic.c" ) foreach(deps IN LISTS tests) string(REPLACE "," ";${CMAKE_SOURCE_DIR}/" deps "${deps}") @@ -174,6 +176,7 @@ if(HAIO_TEST) list(REMOVE_AT deps 0) add_executable("${tests_name}" "${CMAKE_SOURCE_DIR}/tests/${tests_name}.c;${deps}") target_include_directories(${tests_name} PRIVATE "${CMAKE_SOURCE_DIR}/include;${CMAKE_BINARY_DIR}/include") + add_dependencies(${tests_name} haio_functions haio_codec_paths) add_test(NAME "${tests_name}" COMMAND "${tests_name}") endforeach() endif() diff --git a/src/Backend_Workers/pipeline.c b/src/Backend_Workers/pipeline.c index 7531e16..9a31a14 100644 --- a/src/Backend_Workers/pipeline.c +++ b/src/Backend_Workers/pipeline.c @@ -23,30 +23,68 @@ void PipelineBegin(haio_pipeline_t* pipe, haio_type_t first_step) { * @details add tasks to streaming pipeline * @param [in,out] pipe */ -void PipelineStepAdd(haio_pipeline_t* pipe, haio_type_t next_step) { +int PipelineStepAdd(haio_pipeline_t* pipe, haio_type_t next_step) { + static char path_not_found[] = "pipeline path not found"; + static char worker_limit_exceeded[] = "pipeline worker limit exceeded"; haio_worker_func_t workers[HAIO_MAX_STEPS_BY_WORKER]; haio_type_t from = pipe->current_format; haio_type_t to = next_step; haio_type_t result = HAIO_TYPE_NULL; - uint8_t nworkers = GetCodecWorkersFromFormats(from, to, workers, HAIO_MAX_STEPS_BY_WORKER, &result); + uint8_t nworkers; + + if (PipelineHasError(pipe)) { + return 1; + } + + nworkers = GetCodecWorkersFromFormats( + from, + to, + workers, + HAIO_MAX_STEPS_BY_WORKER, + &result + ); + + if (!nworkers || result == HAIO_TYPE_NULL) { + pipe->error = path_not_found; + return 1; + } + + if (nworkers > HAIO_MAX_WORKERS_BY_PIPE - pipe->worker_count) { + pipe->error = worker_limit_exceeded; + return 1; + } + pipe->current_format = result; pipe->state = HAIO_FSM_PIPE_PREPARE; - memcpy(&pipe->workers[pipe->worker_count], workers, nworkers * sizeof(haio_worker_func_t)); + memcpy( + &pipe->workers[pipe->worker_count], + workers, + nworkers * sizeof(haio_worker_func_t) + ); + pipe->worker_count += nworkers; + + return 0; } /** * @details finish pipeline tasks * @param [in,out] pipe */ -void PipelineEnd(haio_pipeline_t* pipe, haio_type_t last_step) { - PipelineStepAdd(pipe, last_step); +int PipelineEnd(haio_pipeline_t* pipe, haio_type_t last_step) { + if (PipelineStepAdd(pipe, last_step)) { + return 1; + } + pipe->state = HAIO_FSM_PIPE_RUNNING; + for (uint8_t i = 1; i < pipe->worker_count; i++) { pipe->handlers[i].canvas.parent = &pipe->handlers[i - 1].canvas; } + + return 0; } /** diff --git a/src/Common_String/get_format_from_magic.c b/src/Common_String/get_format_from_magic.c new file mode 100644 index 0000000..2448828 --- /dev/null +++ b/src/Common_String/get_format_from_magic.c @@ -0,0 +1,18 @@ +#include +#include + +#include "haio.h" + +haio_type_t GetFormatFromMagic(const unsigned char *buffer, size_t len) +{ + static const unsigned char png_signature[] = { + 0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n' + }; + + if (len >= sizeof(png_signature) && + memcmp(buffer, png_signature, sizeof(png_signature)) == 0) { + return HAIO_TYPE_IMG_PNG; + } + + return HAIO_TYPE_NULL; +} diff --git a/src/Frontend_Convert/cli.c b/src/Frontend_Convert/cli.c index b6a45e4..ffd491d 100644 --- a/src/Frontend_Convert/cli.c +++ b/src/Frontend_Convert/cli.c @@ -4,41 +4,182 @@ #include "haio.h" #include "haio/functions.h" +#include "convert.h" -#define BUFFER_SIZE 1024 +#define BUFFER_SIZE 4096 -int FrontendConvertCli(int argc, char* argv[]) { - if (argc <= 2) { - printf("usage:\n./img convert input.png output.ppm\n"); - return 1; +static void PrintConvertUsage(void) +{ + printf("usage:\n"); + printf("haio convert input.png [options] output.ppm\n"); + printf("haio convert png:- output.ppm\n"); + printf("\n"); + printf("options:\n"); + printf(" -crop crop image using the default crop filter\n"); +} + +static void PrintConvertBuildError(convert_command_t *cmd, haio_pipeline_t *pipe) +{ + if (cmd->error.message) { + convert_print_error(cmd->error); + return; } - size_t nbytes; - char buffer[4096]; - FILE* f_in = fopen(argv[1], "rb"); - FILE* f_out = fopen(argv[2], "wb"); + if (PipelineHasError(pipe)) { + printf("[error] %s\n", GetPipelineError(pipe)); + } +} - haio_pipeline_t pipe; - PipelineBegin(&pipe, HAIO_TYPE_BUFFER); - PipelineStepAdd(&pipe, HAIO_TYPE_IMG_PNG); - //PipelineStepAdd(&pipe, HAIO_TYPE_FILTER_CROP); - PipelineEnd(&pipe, HAIO_TYPE_IMG_PPM); - - while(PipelineIsRunning(&pipe)) { - nbytes = fread(buffer, sizeof(char), sizeof(buffer), f_in); - nbytes = PipelineProcess(&pipe, buffer, nbytes, buffer, sizeof(buffer)); - fwrite(buffer, sizeof(char), nbytes, f_out); +static int OpenConvertInput(convert_command_t *cmd, FILE **f_in) +{ + int err = 1; + + do { + if (cmd->has_generator) { + *f_in = NULL; + err = 0; + break; + } + + if (cmd->input_format_name && cmd->input_format == HAIO_TYPE_NULL) { + convert_print_unsupported_format("input", cmd->input_format_name, cmd->input_path); + break; + } + + if (strcmp(cmd->input_path, "-") == 0) { + *f_in = stdin; + err = 0; + break; + } + + *f_in = fopen(cmd->input_path, "rb"); + if (!*f_in) { + printf("[error] could not open input: %s\n", cmd->input_path); + break; + } + + if (!cmd->input_format_name) { + cmd->input_format = convert_input_format_from_file(*f_in, cmd->input_path); + } + + if (cmd->input_format == HAIO_TYPE_NULL) { + convert_print_unsupported_format("input", cmd->input_format_name, cmd->input_path); + fclose(*f_in); + *f_in = NULL; + break; + } + + err = 0; } + while(0); + + return err; +} + +static int OpenConvertOutput(convert_command_t *cmd, FILE **f_out) +{ + int err = 1; - if (PipelineHasError(&pipe)) { - printf("[error] %s", GetPipelineError(&pipe)); - return 1; + do { + if (cmd->output_is_stdout) { + *f_out = stdout; + err = 0; + break; + } + + *f_out = fopen(cmd->output_path, "wb"); + if (!*f_out) { + printf("[error] could not open output: %s\n", cmd->output_path); + break; + } + + err = 0; } + while(0); + + return err; +} + +int FrontendConvertCli(int argc, char* argv[]) +{ + int err = 1; + int write_error = 0; + size_t nread; + size_t nwrite; + char buffer[BUFFER_SIZE]; - //printf("feito!"); + FILE* f_in = NULL; + FILE* f_out = NULL; - fclose(f_in); - fclose(f_out); + convert_command_t cmd; + haio_pipeline_t pipe; + + do { + if (argc <= 2) { + PrintConvertUsage(); + break; + } + + if (convert_tokenize_args(&cmd, argc, argv)) { + convert_print_error(cmd.error); + break; + } + + if (cmd.output_format == HAIO_TYPE_NULL) { + convert_print_unsupported_format("output", cmd.output_format_name, argv[argc - 1]); + break; + } + + if (OpenConvertInput(&cmd, &f_in)) { + break; + } + + if (convert_build_pipeline(&cmd, &pipe)) { + PrintConvertBuildError(&cmd, &pipe); + break; + } + + if (OpenConvertOutput(&cmd, &f_out)) { + break; + } + + while(PipelineIsRunning(&pipe)) { + nread = fread(buffer, sizeof(char), sizeof(buffer), f_in); + + nwrite = PipelineProcess(&pipe, buffer, nread, buffer, sizeof(buffer)); + + if (nwrite && fwrite(buffer, sizeof(char), nwrite, f_out) != nwrite) { + printf("[error] could not write output: %s\n", cmd.output_path); + write_error = 1; + break; + } + } + + if (write_error) { + break; + } + + if (ferror(f_in)) { + printf("[error] could not read input: %s\n", cmd.input_path); + break; + } + + if (PipelineHasError(&pipe)) { + printf("[error] %s\n", GetPipelineError(&pipe)); + break; + } + + err = 0; + } + while(0); + + if (f_in && f_in != stdin) { + fclose(f_in); + } + + if (f_out && f_out != stdout) { + fclose(f_out); + } - return 0; + return err; } diff --git a/src/Frontend_Convert/convert.h b/src/Frontend_Convert/convert.h new file mode 100644 index 0000000..94a32eb --- /dev/null +++ b/src/Frontend_Convert/convert.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include +#include + +#include "haio.h" + +#define CONVERT_MAX_TOKENS 32 +#define CONVERT_FORMAT_PROBE_SIZE 16 + +typedef enum { + CONVERT_TOKEN_INPUT_FILE, + CONVERT_TOKEN_OUTPUT_FILE, + CONVERT_TOKEN_GENERATOR_XC, + CONVERT_TOKEN_GENERATOR_GRADIENT, + CONVERT_TOKEN_FILTER_CROP, + CONVERT_TOKEN_FILTER_FX +} convert_token_type_t; + +typedef struct { + convert_token_type_t type; + char *value; + char *arg; + haio_type_t format; +} convert_token_t; + +typedef struct { + char *message; + char *token; +} convert_error_t; + +typedef struct { + convert_error_t error; + uint8_t token_count; + bool has_input; + bool has_generator; + bool output_is_stdout; + char *input_path; + char *output_path; + char *input_format_name; + char *output_format_name; + haio_type_t input_format; + haio_type_t output_format; + convert_token_t tokens[CONVERT_MAX_TOKENS]; +} convert_command_t; + +haio_type_t convert_format_from_path(char *path); +haio_type_t convert_input_format_from_file(FILE *f_in, char *path); +void convert_print_unsupported_format(const char *kind, char *format_name, char *fallback); +int convert_tokenize_args(convert_command_t *cmd, int argc, char* argv[]); +int convert_build_pipeline(convert_command_t *cmd, haio_pipeline_t *pipe); +void convert_print_error(convert_error_t error); + +bool convert_slice_iequals(char *txt, size_t len, const char *expected); +char *convert_find_format_separator(char *txt); +haio_type_t convert_format_from_name(char *name, size_t len); +bool convert_known_format_prefix(char *name, size_t len); diff --git a/src/Frontend_Convert/format.c b/src/Frontend_Convert/format.c new file mode 100644 index 0000000..f79c21e --- /dev/null +++ b/src/Frontend_Convert/format.c @@ -0,0 +1,108 @@ +#include +#include +#include + +#include "haio.h" +#include "haio/functions.h" +#include "convert.h" + +bool convert_slice_iequals(char *txt, size_t len, const char *expected) +{ + if (strlen(expected) != len) { + return false; + } + + for (size_t i = 0; i < len; i++) { + if (tolower((unsigned char)txt[i]) != tolower((unsigned char)expected[i])) { + return false; + } + } + + return true; +} + +char *convert_find_format_separator(char *txt) +{ + char *p = txt; + + while (*p) { + if (*p == '/' || *p == '\\') { + return NULL; + } + + if (*p == ':') { + return p == txt ? NULL : p; + } + + p++; + } + + return NULL; +} + + +bool convert_known_format_prefix(char *name, size_t len) +{ + return convert_format_from_name(name, len) != HAIO_TYPE_NULL; +} + +haio_type_t convert_format_from_name(char *name, size_t len) +{ + char ext[4] = { 0 }; + + if (!name || !len) { + return HAIO_TYPE_NULL; + } + + if (len > sizeof(ext)) { + return HAIO_TYPE_NULL; + } + + memcpy(ext, name, len); + return GetFormatFromExtension(ext); +} + +haio_type_t convert_format_from_path(char *path) +{ + char *ext = GetExtensionFromString(path); + + if (!ext) { + return HAIO_TYPE_NULL; + } + + return GetFormatFromExtension(ext); +} + +haio_type_t convert_input_format_from_file(FILE *f_in, char *path) +{ + unsigned char buffer[CONVERT_FORMAT_PROBE_SIZE]; + haio_type_t format; + size_t nread; + + nread = fread(buffer, sizeof(unsigned char), sizeof(buffer), f_in); + format = GetFormatFromMagic(buffer, nread); + + if (fseek(f_in, 0, SEEK_SET)) { + return HAIO_TYPE_NULL; + } + + if (format != HAIO_TYPE_NULL) { + return format; + } + + return convert_format_from_path(path); +} + +void convert_print_unsupported_format(const char *kind, char *format_name, char *fallback) +{ + char *sep = format_name ? convert_find_format_separator(format_name) : NULL; + + if (sep) { + printf("[error] unsupported %s format: ", kind); + fwrite(format_name, sizeof(char), (size_t)(sep - format_name), stdout); + printf("\n"); + return; + } + + printf("[error] unknown %s format: %s\n", kind, fallback); +} diff --git a/src/Frontend_Convert/pipeline_builder.c b/src/Frontend_Convert/pipeline_builder.c new file mode 100644 index 0000000..4a40944 --- /dev/null +++ b/src/Frontend_Convert/pipeline_builder.c @@ -0,0 +1,88 @@ +#include + +#include "haio.h" +#include "haio/functions.h" +#include "convert.h" + +static void set_error(convert_command_t *cmd, char *message, char *token) +{ + cmd->error.message = message; + cmd->error.token = token; +} + +static char *unsupported_token_error(convert_token_type_t type) +{ + static char unsupported_generator[] = "generator inputs are not supported yet"; + static char unsupported_fx[] = "-fx is not supported by the pipeline yet"; + static char unsupported_crop_geometry[] = "-crop geometry is not supported by the pipeline yet"; + static char unsupported_token[] = "unsupported convert token"; + + switch (type) { + case CONVERT_TOKEN_GENERATOR_XC: + case CONVERT_TOKEN_GENERATOR_GRADIENT: + return unsupported_generator; + case CONVERT_TOKEN_FILTER_FX: + return unsupported_fx; + case CONVERT_TOKEN_FILTER_CROP: + return unsupported_crop_geometry; + case CONVERT_TOKEN_INPUT_FILE: + case CONVERT_TOKEN_OUTPUT_FILE: + break; + } + + return unsupported_token; +} + +int convert_build_pipeline(convert_command_t *cmd, haio_pipeline_t *pipe) +{ + static char unknown_input_format[] = "unknown input format"; + static char unknown_output_format[] = "unknown output format"; + + if (cmd->has_generator) { + set_error(cmd, unsupported_token_error(CONVERT_TOKEN_GENERATOR_XC), NULL); + return 1; + } + + if (cmd->input_format == HAIO_TYPE_NULL) { + set_error(cmd, unknown_input_format, cmd->input_path); + return 1; + } + + if (cmd->output_format == HAIO_TYPE_NULL) { + set_error(cmd, unknown_output_format, cmd->output_path); + return 1; + } + + PipelineBegin(pipe, HAIO_TYPE_BUFFER); + + if (PipelineStepAdd(pipe, cmd->input_format)) { + return 1; + } + + for (uint8_t i = 0; i < cmd->token_count; i++) { + convert_token_t *token = &cmd->tokens[i]; + + switch (token->type) { + case CONVERT_TOKEN_FILTER_CROP: + if (token->value) { + set_error(cmd, unsupported_token_error(token->type), token->value); + return 1; + } + + if (PipelineStepAdd(pipe, HAIO_TYPE_FILTER_CROP)) { + return 1; + } + break; + case CONVERT_TOKEN_INPUT_FILE: + case CONVERT_TOKEN_OUTPUT_FILE: + break; + case CONVERT_TOKEN_GENERATOR_XC: + case CONVERT_TOKEN_GENERATOR_GRADIENT: + case CONVERT_TOKEN_FILTER_FX: + set_error(cmd, unsupported_token_error(token->type), token->value); + return 1; + } + } + + return PipelineEnd(pipe, cmd->output_format); +} diff --git a/src/Frontend_Convert/tokenize.c b/src/Frontend_Convert/tokenize.c new file mode 100644 index 0000000..508342e --- /dev/null +++ b/src/Frontend_Convert/tokenize.c @@ -0,0 +1,357 @@ +#include +#include +#include +#include +#include + +#include "haio.h" +#include "convert.h" + +static bool string_equals(char *txt, const char *expected) +{ + return strcmp(txt, expected) == 0; +} + +static void convert_command_init(convert_command_t *cmd) +{ + memset(cmd, 0, sizeof(convert_command_t)); +} + +static void convert_set_error(convert_command_t *cmd, char *message, char *token) +{ + cmd->error.message = message; + cmd->error.token = token; +} + +void convert_print_error(convert_error_t error) +{ + if (!error.message) { + return; + } + + if (error.token) { + printf("[error] %s: %s\n", error.message, error.token); + return; + } + + printf("[error] %s\n", error.message); +} + +static bool known_source_prefix(char *name, size_t len) +{ + return convert_slice_iequals(name, len, "xc") || + convert_slice_iequals(name, len, "canvas") || + convert_slice_iequals(name, len, "gradient") || + convert_slice_iequals(name, len, "radial-gradient"); +} + +static bool is_crop_geometry(char *token) +{ + char *p = token; + uint8_t offset_count = 0; + + if (!isdigit((unsigned char)*p)) { + return false; + } + + while (isdigit((unsigned char)*p)) { + p++; + } + + if (*p != 'x') { + return false; + } + + p++; + + if (!isdigit((unsigned char)*p)) { + return false; + } + + while (isdigit((unsigned char)*p)) { + p++; + } + + while (*p == '+' || *p == '-') { + offset_count++; + p++; + + if (!isdigit((unsigned char)*p)) { + return false; + } + + while (isdigit((unsigned char)*p)) { + p++; + } + } + + return *p == '\0' && offset_count != 1; +} + +static int token_add( + convert_command_t *cmd, + convert_token_type_t type, + char *value, + char *arg, + haio_type_t format +) +{ + static char too_many_tokens[] = "too many convert tokens"; + + if (cmd->token_count >= CONVERT_MAX_TOKENS) { + convert_set_error(cmd, too_many_tokens, value); + return 1; + } + + cmd->tokens[cmd->token_count] = (convert_token_t) { + .type = type, + .value = value, + .arg = arg, + .format = format + }; + + cmd->token_count++; + + return 0; +} + +static int tokenize_generator( + convert_command_t *cmd, + convert_token_type_t type, + char *value, + char *size, + char *token +) +{ + static char multiple_inputs[] = "multiple convert inputs are not supported yet"; + + if (cmd->has_input) { + convert_set_error(cmd, multiple_inputs, token); + return 1; + } + + cmd->has_input = true; + cmd->has_generator = true; + + return token_add(cmd, type, value, size, HAIO_TYPE_NULL); +} + +static int tokenize_input_file(convert_command_t *cmd, char *token) +{ + static char multiple_inputs[] = "multiple convert inputs are not supported yet"; + static char missing_format_path[] = "missing path after format prefix"; + char *sep = convert_find_format_separator(token); + haio_type_t format = HAIO_TYPE_NULL; + + if (cmd->has_input) { + convert_set_error(cmd, multiple_inputs, token); + return 1; + } + + cmd->has_input = true; + + if (sep && convert_known_format_prefix(token, (size_t)(sep - token))) { + if (!sep[1]) { + convert_set_error(cmd, missing_format_path, token); + return 1; + } + + format = convert_format_from_name(token, (size_t)(sep - token)); + cmd->input_path = sep + 1; + cmd->input_format_name = token; + cmd->input_format = format; + } else { + cmd->input_path = token; + cmd->input_format = convert_format_from_path(token); + } + + return token_add( + cmd, + CONVERT_TOKEN_INPUT_FILE, + cmd->input_path, + cmd->input_format_name, + cmd->input_format + ); +} + +static int tokenize_output_file(convert_command_t *cmd, char *token) +{ + static char missing_format_path[] = "missing path after format prefix"; + char *sep = convert_find_format_separator(token); + haio_type_t format = HAIO_TYPE_NULL; + + if (sep && convert_known_format_prefix(token, (size_t)(sep - token))) { + if (!sep[1]) { + convert_set_error(cmd, missing_format_path, token); + return 1; + } + + format = convert_format_from_name(token, (size_t)(sep - token)); + cmd->output_path = sep + 1; + cmd->output_format_name = token; + cmd->output_format = format; + } else { + cmd->output_path = token; + cmd->output_format = convert_format_from_path(token); + } + + cmd->output_is_stdout = string_equals(cmd->output_path, "-"); + + return token_add( + cmd, + CONVERT_TOKEN_OUTPUT_FILE, + cmd->output_path, + cmd->output_format_name, + cmd->output_format + ); +} + +static int tokenize_source(convert_command_t *cmd, char *token, char *size) +{ + char *sep = convert_find_format_separator(token); + + if (!sep) { + return tokenize_input_file(cmd, token); + } + + if (convert_slice_iequals(token, (size_t)(sep - token), "xc") || + convert_slice_iequals(token, (size_t)(sep - token), "canvas")) { + return tokenize_generator( + cmd, + CONVERT_TOKEN_GENERATOR_XC, + sep + 1, + size, + token + ); + } + + if (convert_slice_iequals(token, (size_t)(sep - token), "gradient") || + convert_slice_iequals(token, (size_t)(sep - token), "radial-gradient")) { + return tokenize_generator( + cmd, + CONVERT_TOKEN_GENERATOR_GRADIENT, + sep + 1, + size, + token + ); + } + + return tokenize_input_file(cmd, token); +} + +int convert_tokenize_args(convert_command_t *cmd, int argc, char* argv[]) +{ + static char missing_input[] = "missing convert input"; + static char missing_option_arg[] = "missing convert option argument"; + static char unknown_option[] = "unknown convert option"; + static char size_without_generator[] = "-size must be followed by a generator source"; + char *pending_size = NULL; + + convert_command_init(cmd); + + if (argc <= 2) { + convert_set_error(cmd, missing_input, NULL); + return 1; + } + + for (int i = 1; i < argc - 1; i++) { + char *token = argv[i]; + + if (string_equals(token, "-size")) { + if ((i + 1) >= argc - 1) { + convert_set_error(cmd, missing_option_arg, token); + return 1; + } + + pending_size = argv[++i]; + continue; + } + + if (string_equals(token, "-fx")) { + if (pending_size) { + convert_set_error(cmd, size_without_generator, pending_size); + return 1; + } + + if ((i + 1) >= argc - 1) { + convert_set_error(cmd, missing_option_arg, token); + return 1; + } + + if (token_add( + cmd, + CONVERT_TOKEN_FILTER_FX, + argv[++i], + NULL, + HAIO_TYPE_NULL + )) { + return 1; + } + + pending_size = NULL; + continue; + } + + if (string_equals(token, "-crop")) { + char *crop = NULL; + + if (pending_size) { + convert_set_error(cmd, size_without_generator, pending_size); + return 1; + } + + if ((i + 1) < argc - 1 && is_crop_geometry(argv[i + 1])) { + crop = argv[++i]; + } + + if (token_add( + cmd, + CONVERT_TOKEN_FILTER_CROP, + crop, + NULL, + HAIO_TYPE_FILTER_CROP + )) { + return 1; + } + + pending_size = NULL; + continue; + } + + if (token[0] == '-' && !string_equals(token, "-")) { + convert_set_error(cmd, unknown_option, token); + return 1; + } + + if (pending_size) { + char *sep = convert_find_format_separator(token); + + if (!sep || !known_source_prefix(token, (size_t)(sep - token))) { + convert_set_error(cmd, size_without_generator, pending_size); + return 1; + } + } + + if (tokenize_source(cmd, token, pending_size)) { + return 1; + } + + pending_size = NULL; + } + + if (pending_size) { + convert_set_error(cmd, size_without_generator, pending_size); + return 1; + } + + if (!cmd->has_input) { + convert_set_error(cmd, missing_input, NULL); + return 1; + } + + if (tokenize_output_file(cmd, argv[argc - 1])) { + return 1; + } + + return 0; +} diff --git a/tests/test_convert_tokenize_args.c b/tests/test_convert_tokenize_args.c new file mode 100644 index 0000000..a577e36 --- /dev/null +++ b/tests/test_convert_tokenize_args.c @@ -0,0 +1,157 @@ +#include +#include + +#include "haio.h" +#include "../src/Frontend_Convert/convert.h" + +int main(void) +{ + convert_command_t cmd; + { + char *argv[] = { "convert", "input.png", "-crop", "output.ppm" }; + assert(!convert_tokenize_args(&cmd, 4, argv)); + assert(cmd.token_count == 3); + assert(cmd.tokens[0].type == CONVERT_TOKEN_INPUT_FILE); + assert(cmd.tokens[1].type == CONVERT_TOKEN_FILTER_CROP); + assert(cmd.tokens[2].type == CONVERT_TOKEN_OUTPUT_FILE); + assert(cmd.input_format == HAIO_TYPE_IMG_PNG); + assert(cmd.output_format == HAIO_TYPE_IMG_PPM); + } + { + char *argv[] = { + "convert", "input.png", "-crop", "10x10+5+5", "output.ppm" + }; + assert(!convert_tokenize_args(&cmd, 5, argv)); + assert(cmd.token_count == 3); + assert(cmd.tokens[1].type == CONVERT_TOKEN_FILTER_CROP); + assert(strcmp(cmd.tokens[1].value, "10x10+5+5") == 0); + } + { + char *argv[] = { + "convert", "input.png", "-crop", "10x10", "output.ppm" + }; + assert(!convert_tokenize_args(&cmd, 5, argv)); + assert(cmd.token_count == 3); + assert(cmd.tokens[1].type == CONVERT_TOKEN_FILTER_CROP); + assert(strcmp(cmd.tokens[1].value, "10x10") == 0); + } + { + char *argv[] = { + "convert", "input.png", "-crop", "10x10-5+5", "output.ppm" + }; + assert(!convert_tokenize_args(&cmd, 5, argv)); + assert(cmd.token_count == 3); + assert(cmd.tokens[1].type == CONVERT_TOKEN_FILTER_CROP); + assert(strcmp(cmd.tokens[1].value, "10x10-5+5") == 0); + } + { + char *argv[] = { + "convert", "-size", "512x512", "xc:white", "-fx", "j/h", "out.png" + }; + assert(!convert_tokenize_args(&cmd, 7, argv)); + assert(cmd.token_count == 3); + assert(cmd.has_generator); + assert(cmd.tokens[0].type == CONVERT_TOKEN_GENERATOR_XC); + assert(strcmp(cmd.tokens[0].value, "white") == 0); + assert(strcmp(cmd.tokens[0].arg, "512x512") == 0); + assert(cmd.tokens[1].type == CONVERT_TOKEN_FILTER_FX); + assert(strcmp(cmd.tokens[1].value, "j/h") == 0); + assert(cmd.tokens[2].type == CONVERT_TOKEN_OUTPUT_FILE); + assert(cmd.output_format == HAIO_TYPE_IMG_PNG); + } + { + char *argv[] = { "convert", "input.png", "ppm:-" }; + assert(!convert_tokenize_args(&cmd, 3, argv)); + assert(cmd.output_is_stdout); + assert(strcmp(cmd.output_path, "-") == 0); + assert(cmd.output_format == HAIO_TYPE_IMG_PPM); + } + { + char *argv[] = { "convert", "png:-", "ppm:-" }; + assert(!convert_tokenize_args(&cmd, 3, argv)); + assert(strcmp(cmd.input_path, "-") == 0); + assert(cmd.input_format == HAIO_TYPE_IMG_PNG); + assert(cmd.output_is_stdout); + } + { + char *argv[] = { "convert", "PNG:-", "PPM:-" }; + assert(!convert_tokenize_args(&cmd, 3, argv)); + assert(cmd.input_format == HAIO_TYPE_IMG_PNG); + assert(cmd.output_format == HAIO_TYPE_IMG_PPM); + assert(cmd.output_is_stdout); + } + { + char *argv[] = { "convert", "foo:bar.png", "out.ppm" }; + assert(!convert_tokenize_args(&cmd, 3, argv)); + assert(strcmp(cmd.input_path, "foo:bar.png") == 0); + assert(cmd.input_format == HAIO_TYPE_IMG_PNG); + } + { + char *argv[] = { "convert", "input.png", "foo:out.ppm" }; + assert(!convert_tokenize_args(&cmd, 3, argv)); + assert(strcmp(cmd.output_path, "foo:out.ppm") == 0); + assert(cmd.output_format == HAIO_TYPE_IMG_PPM); + } + { + char *argv[] = { "convert", "input.png", "png:-" }; + assert(!convert_tokenize_args(&cmd, 3, argv)); + assert(cmd.output_is_stdout); + assert(strcmp(cmd.output_path, "-") == 0); + assert(cmd.output_format_name != NULL); + assert(cmd.output_format == HAIO_TYPE_IMG_PNG); + } + { + char *argv[] = { "convert", "png:renamed.bin", "out.ppm" }; + assert(!convert_tokenize_args(&cmd, 3, argv)); + assert(strcmp(cmd.input_path, "renamed.bin") == 0); + assert(cmd.input_format_name != NULL); + assert(cmd.input_format == HAIO_TYPE_IMG_PNG); + } + { + char *argv[] = { "convert", "png:", "out.ppm" }; + assert(convert_tokenize_args(&cmd, 3, argv)); + assert(cmd.error.message != NULL); + assert(strcmp(cmd.error.token, "png:") == 0); + } + { + char *argv[] = { "convert", "png:-", "ppm:" }; + assert(convert_tokenize_args(&cmd, 3, argv)); + assert(cmd.error.message != NULL); + assert(strcmp(cmd.error.token, "ppm:") == 0); + } + { + char *argv[] = { "convert", "input.png", "-crop", "other.png", "out.ppm" }; + assert(convert_tokenize_args(&cmd, 5, argv)); + assert(cmd.error.message != NULL); + assert(strcmp(cmd.error.token, "other.png") == 0); + } + { + char *argv[] = { "convert", "input.png", "-crop", "10x", "out.ppm" }; + assert(convert_tokenize_args(&cmd, 5, argv)); + assert(cmd.error.message != NULL); + assert(strcmp(cmd.error.token, "10x") == 0); + } + { + char *argv[] = { "convert", "input.png", "-crop", "10x10+5", "out.ppm" }; + assert(convert_tokenize_args(&cmd, 5, argv)); + assert(cmd.error.message != NULL); + assert(strcmp(cmd.error.token, "10x10+5") == 0); + } + { + char *argv[] = { "convert", "-size", "out.ppm" }; + assert(convert_tokenize_args(&cmd, 3, argv)); + assert(cmd.error.message != NULL); + } + { + char *argv[] = { "convert", "-size", "512x512", "-crop", "xc:white", "out.ppm" }; + assert(convert_tokenize_args(&cmd, 6, argv)); + assert(cmd.error.message != NULL); + } + { + char *argv[] = { "convert", "input.png", "-wat", "out.ppm" }; + assert(convert_tokenize_args(&cmd, 4, argv)); + assert(cmd.error.message != NULL); + assert(strcmp(cmd.error.token, "-wat") == 0); + } + return 0; +} diff --git a/tests/test_get_codec_workers_from_formats.c b/tests/test_get_codec_workers_from_formats.c index 9b8c8f2..c30ae52 100644 --- a/tests/test_get_codec_workers_from_formats.c +++ b/tests/test_get_codec_workers_from_formats.c @@ -11,18 +11,22 @@ int main() { - haio_worker_func_t *workers; + haio_worker_func_t workers[10]; haio_type_t output; { + output = HAIO_TYPE_NULL; assert(GetCodecWorkersFromFormats(HAIO_TYPE_BUFFER, HAIO_TYPE_IMG_PNG, NULL, 0, &output)); assert(output == HAIO_TYPE_IMG_RGBA8); } { - assert(GetCodecWorkersFromFormats(HAIO_TYPE_IMG_PNG, HAIO_TYPE_IMG_PPM, workers, 10, &output)); + output = HAIO_TYPE_NULL; + assert(GetCodecWorkersFromFormats(HAIO_TYPE_IMG_RGBA8, HAIO_TYPE_IMG_PPM, workers, 10, &output)); assert(output == HAIO_TYPE_IMG_PPM); } { - assert(GetCodecWorkersFromFormats(HAIO_TYPE_IMG_PNG, HAIO_TYPE_IMG_Y4M420, NULL, 0, NULL)); + output = HAIO_TYPE_NULL; + assert(GetCodecWorkersFromFormats(HAIO_TYPE_BUFFER, HAIO_TYPE_IMG_Y4M420, NULL, 0, &output)); + assert(output == HAIO_TYPE_IMG_Y4M420); } return 0; } diff --git a/tests/test_get_format_from_magic.c b/tests/test_get_format_from_magic.c new file mode 100644 index 0000000..57c2b93 --- /dev/null +++ b/tests/test_get_format_from_magic.c @@ -0,0 +1,19 @@ +#include +#include + +#include "haio.h" +#include "haio/functions.h" + +int main() +{ + static const unsigned char png[] = { + 0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n' + }; + static const unsigned char ppm[] = { 'P', '6', '\n' }; + + assert(GetFormatFromMagic(png, sizeof(png)) == HAIO_TYPE_IMG_PNG); + assert(GetFormatFromMagic(ppm, sizeof(ppm)) == HAIO_TYPE_NULL); + assert(GetFormatFromMagic(NULL, 0) == HAIO_TYPE_NULL); + + return 0; +}