// Copyright (C) 2014  Davis E. King (davis@dlib.net), Nils Labugt
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_JPEG_SAVER_CPp_
#define DLIB_JPEG_SAVER_CPp_

// only do anything with this file if DLIB_JPEG_SUPPORT is defined
#ifdef DLIB_JPEG_SUPPORT

#include "../array2d.h"
#include "../pixel.h"
#include "save_jpeg.h"
#include <stdio.h>
#include <sstream>
#include <setjmp.h>
#include "image_saver.h"

#ifdef DLIB_JPEG_STATIC
#   include "../external/libjpeg/jpeglib.h"
#else
#   include <jpeglib.h>
#endif

namespace dlib
{

// ----------------------------------------------------------------------------------------

    struct jpeg_saver_error_mgr 
    {
        jpeg_error_mgr pub;    /* "public" fields */
        jmp_buf setjmp_buffer;  /* for return to caller */
    };

    void jpeg_saver_error_exit (j_common_ptr cinfo)
    {
        /* cinfo->err really points to a jpeg_saver_error_mgr struct, so coerce pointer */
        jpeg_saver_error_mgr* myerr = (jpeg_saver_error_mgr*) cinfo->err;

        /* Return control to the setjmp point */
        longjmp(myerr->setjmp_buffer, 1);
    }

// ----------------------------------------------------------------------------------------

    void save_jpeg (
        const array2d<rgb_pixel>& img,
        const std::string& filename,
        int quality
    )
    {
        // make sure requires clause is not broken
        DLIB_CASSERT(img.size() != 0,
            "\t save_jpeg()"
            << "\n\t You can't save an empty image as a JPEG."
            );
        DLIB_CASSERT(0 <= quality && quality <= 100,
            "\t save_jpeg()"
            << "\n\t Invalid quality value."
            << "\n\t quality: " << quality
            );

        FILE* outfile = fopen(filename.c_str(), "wb");
        if (!outfile)
            throw image_save_error("Can't open file " + filename + " for writing.");

        jpeg_compress_struct cinfo;

        jpeg_saver_error_mgr jerr;
        cinfo.err = jpeg_std_error(&jerr.pub);
        jerr.pub.error_exit = jpeg_saver_error_exit;
        /* Establish the setjmp return context for my_error_exit to use. */
        if (setjmp(jerr.setjmp_buffer)) 
        {
            /* If we get here, the JPEG code has signaled an error.
             * We need to clean up the JPEG object, close the input file, and return.
             */
            jpeg_destroy_compress(&cinfo);
            fclose(outfile);
            throw image_save_error("save_jpeg: error while writing " + filename);
        }
         
        jpeg_create_compress(&cinfo);
        jpeg_stdio_dest(&cinfo, outfile);
         
        cinfo.image_width      = img.nc();
        cinfo.image_height     = img.nr();
        cinfo.input_components = 3;
        cinfo.in_color_space   = JCS_RGB;
        jpeg_set_defaults(&cinfo);
        jpeg_set_quality (&cinfo, quality, TRUE);
        jpeg_start_compress(&cinfo, TRUE);
         
        // now write out the rows one at a time
        while (cinfo.next_scanline < cinfo.image_height) {
            JSAMPROW row_pointer = (JSAMPROW) &img[cinfo.next_scanline][0];
            jpeg_write_scanlines(&cinfo, &row_pointer, 1);
        }

        jpeg_finish_compress(&cinfo);
        jpeg_destroy_compress(&cinfo);
        fclose( outfile );
    }

// ----------------------------------------------------------------------------------------

    void save_jpeg (
        const array2d<unsigned char>& img,
        const std::string& filename,
        int quality
    )
    {
        // make sure requires clause is not broken
        DLIB_CASSERT(img.size() != 0,
            "\t save_jpeg()"
            << "\n\t You can't save an empty image as a JPEG."
            );
        DLIB_CASSERT(0 <= quality && quality <= 100,
            "\t save_jpeg()"
            << "\n\t Invalid quality value."
            << "\n\t quality: " << quality
            );


        FILE* outfile = fopen(filename.c_str(), "wb");
        if (!outfile)
            throw image_save_error("Can't open file " + filename + " for writing.");

        jpeg_compress_struct cinfo;

        jpeg_saver_error_mgr jerr;
        cinfo.err = jpeg_std_error(&jerr.pub);
        jerr.pub.error_exit = jpeg_saver_error_exit;
        /* Establish the setjmp return context for my_error_exit to use. */
        if (setjmp(jerr.setjmp_buffer)) 
        {
            /* If we get here, the JPEG code has signaled an error.
             * We need to clean up the JPEG object, close the input file, and return.
             */
            jpeg_destroy_compress(&cinfo);
            fclose(outfile);
            throw image_save_error("save_jpeg: error while writing " + filename);
        }
         
        jpeg_create_compress(&cinfo);
        jpeg_stdio_dest(&cinfo, outfile);
         
        cinfo.image_width      = img.nc();
        cinfo.image_height     = img.nr();
        cinfo.input_components = 1;
        cinfo.in_color_space   = JCS_GRAYSCALE;
        jpeg_set_defaults(&cinfo);
        jpeg_set_quality (&cinfo, quality, TRUE);
        jpeg_start_compress(&cinfo, TRUE);
         
        // now write out the rows one at a time
        while (cinfo.next_scanline < cinfo.image_height) {
            JSAMPROW row_pointer = (JSAMPROW) &img[cinfo.next_scanline][0];
            jpeg_write_scanlines(&cinfo, &row_pointer, 1);
        }

        jpeg_finish_compress(&cinfo);
        jpeg_destroy_compress(&cinfo);
        fclose( outfile );
    }

// ----------------------------------------------------------------------------------------

}

#endif // DLIB_JPEG_SUPPORT

#endif // DLIB_JPEG_SAVER_CPp_