Revisions:Revision 1Revision 2 (you are here)Revision 3png2jpeg is a simple command-line converter of PNG to JPEG.The source code requires the libpng and libjpeg development libraries to be installed, and can be built with a C99 standard-compliant C compiler.My intent is to ensure that the source code is readable, understandable, maintainable and has no bugs; in simpler words, that it be of high quality.Listing of png2jpeg.c://// Copyright (c) 2016 Andrei Bondor//// Permission is hereby granted, free of charge, to any person obtaining a copy// of this software and associated documentation files (the Software), to deal// in the Software without restriction, including without limitation the rights// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell// copies of the Software, and to permit persons to whom the Software is// furnished to do so, subject to the following conditions://// The above copyright notice and this permission notice shall be included in// all copies or substantial portions of the Software.//// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN// THE SOFTWARE.//#include <stdbool.h>#include <stddef.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <jpeglib.h>#include <png.h>////// The version can be thought of as a specially written integer./// Thus, for example, the version following 1.0.2.9 is 1.0.3.0./// The version shall never be something like 1.0.3210.4.///#define PNG2JPEG_VERSION            1.0.0.1////// @brief Restricts Var's value to the [Min, Max] interval./// @param [in,out] Var             Variable to be clamped./// @param [in] Min                 Minimum permitted value for the variable./// @param [in] Max                 Maximum permitted value for the variable./// @pre `Min < Max`///#define CLAMP(Var, Min, Max) if (true) {                                       \    if ((Var) < (Min)) (Var) = (Min); else                                     \    if ((Var) > (Max)) (Var) = (Max);                                          \} else (void)0////// @brief Prints help information.///static void print_help(void){    printf(\n===============================================================);    printf(================\nPNG-TO-JPEG Conversion Utility                 );    printf(                 version  PNG2JPEG_VERSION \n==================);    printf(=============================================================\n\n);    printf(The syntax is:\n\n    png2jpeg input_file\n    png2jpeg [option <);    printf(arg> [...]] input_file\n\nThe following options are supported:\n );    printf(\n    LONG NAME       SHORTHAND   ARGUMENT        MEANING\n    --);    printf(-----------------------------------------------------------------);    printf(--------\n    --help          -h          (none)          print h);    printf(elp information\n    --verbose       -v          (none)          );    printf(print debug information\n    --grayscale     -g          (none)  );    printf(        create monochrome JPEG\n    --progressive   -p          ();    printf(none)          create progressive JPEG\n    --quality       -q   );    printf(       0 to 100        set quality\n    --outfile       -o       );    printf(   filename        override output filename\n    --sample        );    printf(-s          HxV             set component sampling factors\n    -);    printf(-background    -b          R,G,B           set background color\n);    printf(    -------------------------------------------------------------);    printf(--------------\n\nExamples:\n\n    png2jpeg input.png\n    png2jp);    printf(eg -p --quality 95 --sample 1x1 -o converted.jpeg original.png\n );    printf(   png2jpeg -q 50 --background 128,255,0 icon.png\n\n            );}////// @brief Prints a complaint about an option and its argument./// @param [in] option              Option to complain about./// @param [in] arg                 Argument to complain about./// @note If both `option` and `arg` are `NULL` then the complaint is generic./// @note If `option` is `NULL` then the complaint is about `arg` being invalid./// @note If `arg` is `NULL` then the complaint is about `option` missing its///  argument.///static void print_bad_params(const char *option, const char *arg){    if (option == NULL)    {        if (arg == NULL)            fprintf(stderr, png2jpeg error: bad option and/or argument\n);        else            fprintf(stderr, png2jpeg error: invalid argument \%s\\n, arg);    }    else    if (arg == NULL)    {        fprintf(stderr, png2jpeg error: option \%s\ has no argument\n,            option);    }    else    {        fprintf(stderr, png2jpeg error: option \%s\ has invalid argument             \%s\\n, option, arg);    }}////// @brief Checks if an option was given in the parameters./// @param argc                     Number of parameters./// @param [in] argv                Parameter strings./// @param [in] option              Option string to be searched./// @pre `argv != NULL && option != NULL`/// @returns Whether or not the option was given./// @retval true                    The option was found./// @retval false                   The option was not found.///static bool option_given(int argc, char *argv[], const char *option){    for (int i=0; i < argc; ++i)        if (strcmp(argv[i], option) == 0)            return true;    return false;}////// @brief Searches the parameters and retrieves an argument string./// @param argc                     Number of parameters./// @param [in] argv                Parameter strings./// @param [in] option              Option string to be searched./// @pre `argv != NULL && option != NULL`/// @returns The argument provided for `option`./// @retval NULL                    No argument associated with `option`.///static const char * option_arg(int argc, char *argv[], const char *option){    for (int i=0; i < argc; ++i)        if (strcmp(argv[i], option) == 0)        {            // NOTE: according to the C99 standard (Section 5.1.2.2.1/2):            //  argv[argc] shall be a null pointer            return argv[i + 1];        }    return NULL;}////// @brief Holds the settings for the PNG to JPEG conversion./// @remarks MSF refers to the MAX_SAMP_FACTOR macro.///typedef struct{    bool        grayscale;          ///< Grayscale JPEG.    bool        progressive;        ///< Progressive JPEG.    int         quality;            ///< Quality, 0 to 100.    int         samp_h;             ///< Sample factor for H, 1 to MSF.    int         samp_v;             ///< Sample factor for V, 1 to MSF.    png_color   bgc;                ///< Background color, for transparency.} png2jpeg_settings;////// @brief Prints the conversion settings./// @param [in] config              Configuration to be printed.///static void print_settings(const png2jpeg_settings *config){    printf(\nConversion Settings\n-------------------\n);    printf(Grayscale:          %s\n, config->grayscale ? YES : NO);    printf(Progressive:        %s\n, config->progressive ? YES : NO);    printf(Quality:            %d\n, config->quality);    printf(Sample factors:     %dx%d\n, config->samp_h, config->samp_v);    printf(Background color:   (%d,%d,%d)\n\n,        config->bgc.red, config->bgc.green, config->bgc.blue);}////// @brief Configures the Grayscale JPEG option./// @param argc                     Number of parameters, from `main()`./// @param [in] argv                Parameter strings, from `main()`./// @param [in,out] config          Configuration to be updated.///static void config_grayscale(int argc, char *argv[], png2jpeg_settings *config){    if (option_given(argc, argv, --grayscale) ||        option_given(argc, argv, -g))    {        config->grayscale = true;    }}////// @brief Configures the Progressive JPEG option./// @param argc                     Number of parameters, from `main()`./// @param [in] argv                Parameter strings, from `main()`./// @param [in,out] config          Configuration to be updated.///static void config_progressive(int argc, char *argv[],    png2jpeg_settings *config){    if (option_given(argc, argv, --progressive) ||        option_given(argc, argv, -p))    {        config->progressive = true;    }}////// @brief Configures the Quality option./// @note This function may call `exit()`./// @param argc                     Number of parameters, from `main()`./// @param [in] argv                Parameter strings, from `main()`./// @param [in,out] config          Configuration to be updated.///static void config_quality(int argc, char *argv[], png2jpeg_settings *config){    const char *temp_option, *temp_arg;    if (option_given(argc, argv, --quality))    {        temp_option = --quality;        temp_arg = option_arg(argc, argv, --quality);    }    else    if (option_given(argc, argv, -q))    {        temp_option = -q;        temp_arg = option_arg(argc, argv, -q);    }    else        return;    if (temp_arg == NULL)    {        print_bad_params(temp_option, NULL);        exit(EXIT_FAILURE);    }    config->quality = strtol(temp_arg, NULL, 10);    CLAMP(config->quality, 0, 100);}////// @brief Configures the Sample Factors option./// @note This function may call `exit()`./// @param argc                     Number of parameters, from `main()`./// @param [in] argv                Parameter strings, from `main()`./// @param [in,out] config          Configuration to be updated.///static void config_sample(int argc, char *argv[], png2jpeg_settings *config){    const char *temp_option, *temp_arg;    if (option_given(argc, argv, --sample))    {        temp_option = --sample;        temp_arg = option_arg(argc, argv, --sample);    }    else    if (option_given(argc, argv, -s))    {        temp_option = -s;        temp_arg = option_arg(argc, argv, -s);    }    else        return;    if (temp_arg == NULL)    {        print_bad_params(temp_option, NULL);        exit(EXIT_FAILURE);    }    char temp_sep; // Separator    int temp_samp_h, temp_samp_v;    const int temp_ret = sscanf(temp_arg, %d%c%d,        &temp_samp_h, &temp_sep, &temp_samp_v);    if (temp_ret != 3 || (temp_sep != 'x' && temp_sep != '*'))    {        print_bad_params(temp_option, temp_arg);        exit(EXIT_FAILURE);    }    CLAMP(temp_samp_h, 1, MAX_SAMP_FACTOR);    CLAMP(temp_samp_v, 1, MAX_SAMP_FACTOR);    config->samp_h = temp_samp_h;    config->samp_v = temp_samp_v;}////// @brief Configures the Background Color option./// @note This function may call `exit()`./// @param argc                     Number of parameters, from `main()`./// @param [in] argv                Parameter strings, from `main()`./// @param [in,out] config          Configuration to be updated.///static void config_background(int argc, char *argv[], png2jpeg_settings *config){    const char *temp_option, *temp_arg;    if (option_given(argc, argv, --background))    {        temp_option = --background;        temp_arg = option_arg(argc, argv, --background);    }    else    if (option_given(argc, argv, -b))    {        temp_option = -b;        temp_arg = option_arg(argc, argv, -b);    }    else        return;    if (temp_arg == NULL)    {        print_bad_params(temp_option, NULL);        exit(EXIT_FAILURE);    }    int red, green, blue; // Background Color    const int temp_ret = sscanf(temp_arg, %d,%d,%d, &red, &green, &blue);    if (temp_ret != 3)    {        print_bad_params(temp_option, temp_arg);        exit(EXIT_FAILURE);    }    CLAMP(red,      0, 255);    CLAMP(green,    0, 255);    CLAMP(blue,     0, 255);    config->bgc.red     = red;    config->bgc.green   = green;    config->bgc.blue    = blue;}////// @brief Converts PNG image to JPEG image./// @note This function may call `exit()`./// @param [in] input_fn            Input image filename./// @param [in] output_fn           Output image filename./// @param [in] config              Conversion settings./// @param verbose                  Verbose output.///static void png_to_jpeg(const char *input_fn, const char *output_fn,    const png2jpeg_settings *config, bool verbose){    png_image input_img = {        .opaque     = NULL,        .version    = PNG_IMAGE_VERSION    };    if (png_image_begin_read_from_file(&input_img, input_fn) == 0)    {        fprintf(stderr, png2jpeg error: reading PNG);        if (input_img.warning_or_error != 0)            fprintf(stderr, , %s\n, input_img.message);        else            fprintf(stderr, \n);        exit(EXIT_FAILURE);    }    if (config->grayscale)        input_img.format = PNG_FORMAT_GRAY;    else        input_img.format = PNG_FORMAT_RGB;    if (verbose)        // TODO: cast to size_t and use %zu in the future (MinGW 32-bit bug)        printf(PNG image size in memory: %lu bytes\n,            (long unsigned int)PNG_IMAGE_SIZE(input_img));    png_bytep buffer = malloc(PNG_IMAGE_SIZE(input_img));    if (buffer == NULL)    {        // TODO: cast to size_t and use %zu in the future (MinGW 32-bit bug)        fprintf(stderr, png2jpeg error: out of memory (%lu bytes)\n,            (long unsigned int)PNG_IMAGE_SIZE(input_img));        png_image_free(&input_img);        exit(EXIT_FAILURE);    }    if (png_image_finish_read(&input_img, &config->bgc, buffer, 0, NULL) == 0)    {        fprintf(stderr, png2jpeg error: reading PNG);        if (input_img.warning_or_error != 0)            fprintf(stderr, , %s\n, input_img.message);        else            fprintf(stderr, \n);        free(buffer);        exit(EXIT_FAILURE);    }    FILE *output_img = fopen(output_fn, wb);    if (output_img == NULL)    {        fprintf(stderr, png2jpeg error: could not open output file             \%s\ for writing\n, output_fn);        free(buffer);        exit(EXIT_FAILURE);    }    struct jpeg_compress_struct     cinfo;    struct jpeg_error_mgr           jerr;    cinfo.err = jpeg_std_error(&jerr);    jpeg_create_compress(&cinfo);    jpeg_stdio_dest(&cinfo, output_img);    cinfo.image_width   = input_img.width;    cinfo.image_height  = input_img.height;    if (config->grayscale)    {        cinfo.input_components  = PNG_IMAGE_SAMPLE_CHANNELS(PNG_FORMAT_GRAY);        cinfo.in_color_space    = JCS_GRAYSCALE;    }    else    {        cinfo.input_components  = PNG_IMAGE_SAMPLE_CHANNELS(PNG_FORMAT_RGB);        cinfo.in_color_space    = JCS_RGB;    }    const int   row_stride = cinfo.image_width * cinfo.input_components;    JSAMPROW    row_pointer[1];    jpeg_set_defaults(&cinfo);    jpeg_set_quality(&cinfo, config->quality, false);    cinfo.optimize_coding = true;    // TODO: add support for multiple sample factors, as    //  in `set_sample_factors()` from rdswitch.c    cinfo.comp_info[0].h_samp_factor = config->samp_h;    cinfo.comp_info[0].v_samp_factor = config->samp_v;    if (config->progressive)        jpeg_simple_progression(&cinfo);    jpeg_start_compress(&cinfo, true);    while (cinfo.next_scanline < cinfo.image_height)    {        row_pointer[0] = &buffer[cinfo.next_scanline * row_stride];        jpeg_write_scanlines(&cinfo, row_pointer, 1);    }    jpeg_finish_compress(&cinfo);    jpeg_destroy_compress(&cinfo);    fclose(output_img);    free(buffer);}////// @brief Enters the program./// @param argc                     Number of parameters./// @param [in] argv                Parameter strings./// @returns Whether or not the operation was successful./// @retval EXIT_SUCCESS            Operation success./// @retval EXIT_FAILURE            Operation failure.///int main(int argc, char *argv[]){    // sets the verbosity of the program    bool verbose = false;    // PNG to JPEG conversion settings    png2jpeg_settings config = {        .grayscale      = false,        .progressive    = false,        .quality        = 75,        .samp_h         = 2,        .samp_v         = 2,        .bgc            = {.red = 0, .green = 0, .blue = 0}    };    if (argc <= 1 ||        option_given(argc, argv, --help) ||        option_given(argc, argv, -h))    {        print_help();        return EXIT_SUCCESS;    }    if (option_given(argc, argv, --verbose) ||        option_given(argc, argv, -v))    {        verbose = true;    }    config_grayscale    (argc, argv, &config);    config_progressive  (argc, argv, &config);    config_quality      (argc, argv, &config);    config_sample       (argc, argv, &config);    config_background   (argc, argv, &config);    if (verbose)        print_settings(&config);    // Input and Output Image Filenames    // NOTE: (argc - 1) is safe as (argc <= 1) was checked before getting here    const char * const input_fn = argv[argc - 1];    const char *output_fn = NULL;    if (option_given(argc, argv, --outfile))        output_fn = option_arg(argc, argv, --outfile);    else    if (option_given(argc, argv, -o))        output_fn = option_arg(argc, argv, -o);    // Output Image Automatic Extension (period included)    const char * const output_ext = .jpg;    // Output Image Automatic Filename    // FIXME: potential stack overflow vulnerability!    char output_autofn[strlen(input_fn) + strlen(output_ext) + 1];    // check if user overrode the automatic filename; if not, use it    if (output_fn == NULL)    {        strcpy(output_autofn, input_fn);        strcat(output_autofn, output_ext);        output_fn = output_autofn;    }    if (verbose)    {        printf(Input image filename: \%s\\n, input_fn);        printf(Output image filename: \%s\\n, output_fn);    }    png_to_jpeg(input_fn, output_fn, &config, verbose);    return EXIT_SUCCESS;}
png2jpeg, a utility for converting PNG to JPEG (rev. 2/3)
c;image;console