Subversion Repositories Scribus

Rev

Rev 5947 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

#include <qfile.h>
#include <setjmp.h>
#include "scconfig.h"
#include "scimgdataloader_jpeg.h"
#include "exif.h"

#ifdef HAVE_CMS
        #include CMS_INC
#endif

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

static void my_error_exit (j_common_ptr cinfo)
{
        my_error_ptr myerr = (my_error_ptr) cinfo->err;
        (*cinfo->err->output_message) (cinfo);
        longjmp (myerr->setjmp_buffer, 1);
}

#define ICC_MARKER  (JPEG_APP0 + 2)     /* JPEG marker code for ICC */
#define PHOTOSHOP_MARKER  (JPEG_APP0 + 13)      /* JPEG marker code for PHOTOSHOP */
#define ICC_OVERHEAD_LEN  14            /* size of non-profile data in APP2 */
#define MAX_BYTES_IN_MARKER  65533      /* maximum data len of a JPEG marker */
#define MAX_DATA_BYTES_IN_MARKER  (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN)


ScImgDataLoader_JPEG::ScImgDataLoader_JPEG(void)
{
        initSupportedFormatList();
}

void ScImgDataLoader_JPEG::initSupportedFormatList(void)
{
        m_supportedFormats.clear();
        m_supportedFormats.append( "jpg" );
        m_supportedFormats.append( "jpeg" );
}

void ScImgDataLoader_JPEG::loadEmbeddedProfile(const QString& fn)
{
        m_embeddedProfile.resize(0);
        m_profileComponents = 0;
#ifdef HAVE_CMS
        if (!QFile::exists(fn))
                return;
        struct jpeg_decompress_struct cinfo;
        struct my_error_mgr         jerr;
        FILE     *infile;
        cinfo.err = jpeg_std_error (&jerr.pub);
        jerr.pub.error_exit = my_error_exit;
        infile = NULL;
        if (setjmp (jerr.setjmp_buffer))
        {
                jpeg_destroy_decompress (&cinfo);
                if (infile)
                        fclose (infile);
                return;
        }
        jpeg_create_decompress (&cinfo);
        if ((infile = fopen (fn.local8Bit(), "rb")) == NULL)
                return;
        jpeg_stdio_src(&cinfo, infile);
        jpeg_save_markers(&cinfo, ICC_MARKER, 0xFFFF);
        jpeg_read_header(&cinfo, true);
        jpeg_start_decompress(&cinfo);
        unsigned int EmbedLen = 0;
        unsigned char* EmbedBuffer;
        if (read_jpeg_marker(ICC_MARKER,&cinfo, &EmbedBuffer, &EmbedLen))
        {
                cmsHPROFILE prof = cmsOpenProfileFromMem(EmbedBuffer, EmbedLen);
                if (prof)
                {
                        if (static_cast<int>(cmsGetColorSpace(prof)) == icSigRgbData)
                                m_profileComponents = 3;
                        if (static_cast<int>(cmsGetColorSpace(prof)) == icSigCmykData)
                                m_profileComponents = 4;
                        m_embeddedProfile.duplicate((const char*) EmbedBuffer, EmbedLen);
                }
                cmsCloseProfile(prof);
                free(EmbedBuffer);
        }
        (void) jpeg_finish_decompress(&cinfo);
        fclose (infile);
        jpeg_destroy_decompress (&cinfo);
#endif
}

void ScImgDataLoader_JPEG::preloadAlphaChannel(const QString& fn, int res)
{
        // No support for aplha in jpeg pictures
        initialize();
}

bool ScImgDataLoader_JPEG::loadPicture(const QString& fn, int res, bool thumbnail)
{
        bool isCMYK = false;
        float xres = 72.0, yres = 72.0;
        if (!QFile::exists(fn))
                return false;
        ExifData ExifInf;
        struct jpeg_decompress_struct cinfo;
        struct my_error_mgr         jerr;
        FILE     *infile;
        cinfo.err = jpeg_std_error (&jerr.pub);
        jerr.pub.error_exit = my_error_exit;
        infile = NULL;

        initialize();
        m_imageInfoRecord.type = 0;
        m_imageInfoRecord.exifInfo.thumbnail = QImage();
        if (setjmp (jerr.setjmp_buffer))
        {
                jpeg_destroy_decompress (&cinfo);
                if (infile)
                        fclose (infile);
                return false;
        }
        jpeg_create_decompress (&cinfo);
        if ((infile = fopen (fn.local8Bit(), "rb")) == NULL)
                return false;
        jpeg_stdio_src(&cinfo, infile);
        jpeg_save_markers(&cinfo, ICC_MARKER, 0xFFFF);
        jpeg_save_markers(&cinfo, PHOTOSHOP_MARKER, 0xFFFF);
        jpeg_read_header(&cinfo, true);
        jpeg_start_decompress(&cinfo);
        bool exi = ExifInf.scan(fn);
        if ((exi) && (ExifInf.exifDataValid))
        {
                if ((!ExifInf.isNullThumbnail()) && thumbnail)
                        m_image = ExifInf.getThumbnail();
                m_imageInfoRecord.exifInfo.cameraName = ExifInf.getCameraModel();
                m_imageInfoRecord.exifInfo.cameraVendor = ExifInf.getCameraMake();
                m_imageInfoRecord.exifInfo.thumbnail = ExifInf.getThumbnail();
                m_imageInfoRecord.exifInfo.comment = ExifInf.getComment();
                m_imageInfoRecord.exifInfo.width = ExifInf.getWidth();
                m_imageInfoRecord.exifInfo.height = ExifInf.getHeight();
                m_imageInfoRecord.exifInfo.userComment = ExifInf.getUserComment();
                m_imageInfoRecord.exifInfo.dateTime = ExifInf.getDateTime();
                m_imageInfoRecord.exifDataValid = true;
                if (cinfo.density_unit == 0)
                {
                        xres = 72;
                        yres = 72;
                }
                else if ( cinfo.density_unit == 1 )
                {
                        xres = cinfo.X_density;
                        yres = cinfo.Y_density;
                }
                else if ( cinfo.density_unit == 2 )
                {
                        xres = cinfo.X_density * 2.54;
                        yres = cinfo.Y_density * 2.54;
                }
                m_imageInfoRecord.xres = qRound(xres);
                m_imageInfoRecord.yres = qRound(yres);
                if (cinfo.output_components == 4)
                        m_imageInfoRecord.colorspace = 1;
                else if (cinfo.output_components == 3)
                        m_imageInfoRecord.colorspace = 0;
                else if (cinfo.output_components == 1)
                        m_imageInfoRecord.colorspace = 2;
                m_imageInfoRecord.progressive = jpeg_has_multiple_scans(&cinfo);
                if ((!ExifInf.isNullThumbnail()) && thumbnail)
                {
                        jpeg_destroy_decompress(&cinfo);
                        fclose(infile);
                        return true;
                }
        }
        else
                m_imageInfoRecord.exifDataValid = false;
#ifdef HAVE_CMS
        unsigned int EmbedLen = 0;
        unsigned char* EmbedBuffer;
        if (read_jpeg_marker(ICC_MARKER,&cinfo, &EmbedBuffer, &EmbedLen))
        {
                const char *Descriptor;
                cmsHPROFILE prof = cmsOpenProfileFromMem(EmbedBuffer, EmbedLen);
                Descriptor = cmsTakeProductDesc(prof);
                m_embeddedProfile.duplicate((const char*) EmbedBuffer, EmbedLen);
                m_imageInfoRecord.profileName = QString(Descriptor);
                m_imageInfoRecord.isEmbedded = true;
                free(EmbedBuffer);
        }
        else
        {
                m_imageInfoRecord.isEmbedded = false;
                m_imageInfoRecord.profileName = "";
        }
#endif // HAVE_CMS
        unsigned int PhotoshopLen = 0;
        unsigned char * PhotoshopBuffer;
        if (cinfo.density_unit == 0)
        {
                xres = 72;
                yres = 72;
                m_image.setDotsPerMeterX(2834);
                m_image.setDotsPerMeterY(2834);
        }
        else if ( cinfo.density_unit == 1 )
        {
                xres = cinfo.X_density;
                yres = cinfo.Y_density;
                m_image.setDotsPerMeterX( int(100. * cinfo.X_density / 2.54) );
                m_image.setDotsPerMeterY( int(100. * cinfo.Y_density / 2.54) );
        }
        else if ( cinfo.density_unit == 2 )
        {
                xres = cinfo.X_density * 2.54;
                yres = cinfo.Y_density * 2.54;
                m_image.setDotsPerMeterX( int(100. * cinfo.X_density) );
                m_image.setDotsPerMeterY( int(100. * cinfo.Y_density) );
        }
        m_imageInfoRecord.xres = qRound(xres);
        m_imageInfoRecord.yres = qRound(yres);
        if (cinfo.output_components == 4)
        {
                isCMYK = true;
                m_imageInfoRecord.colorspace = 1;
        }
        else if (cinfo.output_components == 3)
                m_imageInfoRecord.colorspace = 0;
        else if (cinfo.output_components == 1)
                m_imageInfoRecord.colorspace = 2;
        m_imageInfoRecord.progressive = jpeg_has_multiple_scans(&cinfo);

        if (read_jpeg_marker(PHOTOSHOP_MARKER,&cinfo, &PhotoshopBuffer, &PhotoshopLen) )
        {
                if (PhotoshopLen != 0)
                {
                        bool savEx = m_imageInfoRecord.exifDataValid;
                        QByteArray arrayPhot(PhotoshopLen);
                        arrayPhot.setRawData((const char*)PhotoshopBuffer,PhotoshopLen);
                        QDataStream strPhot(arrayPhot,IO_ReadOnly);
                        strPhot.setByteOrder( QDataStream::BigEndian );
                        PSDHeader fakeHeader;
                        fakeHeader.width = cinfo.output_width;
                        fakeHeader.height = cinfo.output_height;
                        if (cinfo.output_components == 4)
                                m_imageInfoRecord.colorspace = 1;
                        else if (cinfo.output_components == 3)
                                m_imageInfoRecord.colorspace = 0;
                        else if (cinfo.output_components == 1)
                                m_imageInfoRecord.colorspace = 2;
                        m_imageInfoRecord.progressive = jpeg_has_multiple_scans(&cinfo);
                        parseRessourceData(strPhot, fakeHeader, PhotoshopLen);
                        // Photoshop resolution is more accurate than jpeg header resolution
                        xres = m_imageInfoRecord.xres;
                        yres = m_imageInfoRecord.yres;
                        m_image.setDotsPerMeterX( int(100. * m_imageInfoRecord.xres / 2.54) );
                        m_image.setDotsPerMeterY( int(100. * m_imageInfoRecord.yres / 2.54) );
                        m_imageInfoRecord.valid = (m_imageInfoRecord.PDSpathData.size())>0?true:false; // The only interest is vectormask
                        arrayPhot.resetRawData((const char*)PhotoshopBuffer,PhotoshopLen);
                        free( PhotoshopBuffer );
                        m_imageInfoRecord.exifDataValid = savEx;
                }
        }
        if ( cinfo.output_components == 3 || cinfo.output_components == 4)
                m_image.create( cinfo.output_width, cinfo.output_height, 32 );
        else if ( cinfo.output_components == 1 )
        {
                m_image.create( cinfo.output_width, cinfo.output_height, 8, 256 );
                for (int i=0; i<256; i++)
                        m_image.setColor(i, qRgb(i,i,i));
        }
        if (!m_image.isNull())
        {
                uchar** lines = m_image.jumpTable();
                while (cinfo.output_scanline < cinfo.output_height)
                        (void) jpeg_read_scanlines(&cinfo, lines + cinfo.output_scanline, cinfo.output_height);
                if ( cinfo.output_components == 3 )
                {
                        for (uint j=0; j<cinfo.output_height; j++)
                        {
                                uchar *in = m_image.scanLine(j) + cinfo.output_width * 3;
                                QRgb *out = (QRgb*) m_image.scanLine(j);
                                for (uint i=cinfo.output_width; i--; )
                                {
                                        in -= 3;
                                        out[i] = qRgb(in[0], in[1], in[2]);
                                }
                        }
                }
                if ( cinfo.output_components == 4 )
                {
                        for (int i = 0; i < m_image.height(); i++)
                        {
                                QRgb *ptr = (QRgb*)  m_image.scanLine(i);
                                unsigned char c, m, y ,k;
                                if ((cinfo.jpeg_color_space == JCS_YCCK) || ((cinfo.jpeg_color_space == JCS_CMYK) && (cinfo.saw_Adobe_marker)))
                                {
                                        for (int j = 0; j <  m_image.width(); j++)
                                        {
                                                unsigned char *p = (unsigned char *) ptr;
                                                c = p[0];
                                                m = p[1];
                                                y =  p[2];
                                                k =  p[3];
                                                *ptr = qRgba(255 - c, 255 - m, 255 - y, 255 - k);
                                                ptr++;
                                        }
                                }
                                else
                                {
                                        for (int j = 0; j <  m_image.width(); j++)
                                        {
                                                unsigned char *p = (unsigned char *) ptr;
                                                c = p[0];
                                                m = p[1];
                                                y =  p[2];
                                                k =  p[3];
                                                *ptr = qRgba(y, m, c, k);
                                                ptr++;
                                        }
                                }
                        }
                        isCMYK = true;
                }
                else
                        isCMYK = false;
                if ( cinfo.output_components == 1 )
                {
                        QImage tmpImg = m_image.convertDepth(32);
                         m_image.create( cinfo.output_width, cinfo.output_height, 32 );
                        for( int yi=0; yi < tmpImg.height(); ++yi )
                        {
                                QRgb *s = (QRgb*)(tmpImg.scanLine( yi ));
                                QRgb *d = (QRgb*)(m_image.scanLine( yi ));
                                for(int xi=0; xi < tmpImg.width(); ++xi )
                                {
                                        (*d) = (*s);
                                        s++;
                                        d++;
                                }
                        }
                }
                m_image.setAlphaBuffer(true);
        }
        (void) jpeg_finish_decompress(&cinfo);
        fclose (infile);
        jpeg_destroy_decompress (&cinfo);
        m_imageInfoRecord.layerInfo.clear();
        m_imageInfoRecord.BBoxX = 0;
        m_imageInfoRecord.BBoxH = m_image.height();
        return (!m_image.isNull());
}

bool ScImgDataLoader_JPEG::marker_is_icc (jpeg_saved_marker_ptr marker)
{
        return
            marker->marker == ICC_MARKER &&
            marker->data_length >= ICC_OVERHEAD_LEN &&
            /* verify the identifying string */
            GETJOCTET(marker->data[0]) == 0x49 &&
            GETJOCTET(marker->data[1]) == 0x43 &&
            GETJOCTET(marker->data[2]) == 0x43 &&
            GETJOCTET(marker->data[3]) == 0x5F &&
            GETJOCTET(marker->data[4]) == 0x50 &&
            GETJOCTET(marker->data[5]) == 0x52 &&
            GETJOCTET(marker->data[6]) == 0x4F &&
            GETJOCTET(marker->data[7]) == 0x46 &&
            GETJOCTET(marker->data[8]) == 0x49 &&
            GETJOCTET(marker->data[9]) == 0x4C &&
            GETJOCTET(marker->data[10]) == 0x45 &&
            GETJOCTET(marker->data[11]) == 0x0;
}

bool ScImgDataLoader_JPEG::marker_is_photoshop (jpeg_saved_marker_ptr marker)
{
        return
            marker->marker == PHOTOSHOP_MARKER &&
            marker->data_length >= ICC_OVERHEAD_LEN &&
            /* verify the identifying string */
            GETJOCTET(marker->data[0]) == 0x50 &&
            GETJOCTET(marker->data[1]) == 0x68 &&
            GETJOCTET(marker->data[2]) == 0x6F &&
            GETJOCTET(marker->data[3]) == 0x74 &&
            GETJOCTET(marker->data[4]) == 0x6F &&
            GETJOCTET(marker->data[5]) == 0x73 &&
            GETJOCTET(marker->data[6]) == 0x68 &&
            GETJOCTET(marker->data[7]) == 0x6F &&
            GETJOCTET(marker->data[8]) == 0x70 &&
            GETJOCTET(marker->data[9]) == 0x20 &&
            GETJOCTET(marker->data[10]) == 0x33 &&
            GETJOCTET(marker->data[11]) == 0x2E &&
            GETJOCTET(marker->data[12]) == 0x30 &&
            GETJOCTET(marker->data[13]) == 0x0;
}

/* Small modification of original read_icc_profile method from jpegicc of lcms project
 * to enable read of Photoshop marker
 */

bool ScImgDataLoader_JPEG::read_jpeg_marker (UINT8 requestmarker, j_decompress_ptr cinfo, JOCTET **icc_data_ptr, unsigned int *icc_data_len)
{
        jpeg_saved_marker_ptr marker;
        int num_markers = 0;
        int seq_no;
        JOCTET *icc_data;
        unsigned int total_length;
#define MAX_SEQ_NO  255         /* sufficient since marker numbers are bytes */
        char marker_present[MAX_SEQ_NO+1];        /* 1 if marker found */
        unsigned int data_length[MAX_SEQ_NO+1]; /* size of profile data in marker */
        unsigned int data_offset[MAX_SEQ_NO+1]; /* offset for data in marker */

        *icc_data_ptr = NULL;           /* avoid confusion if false return */
        *icc_data_len = 0;

        /* This first pass over the saved markers discovers whether there are
         * any ICC markers and verifies the consistency of the marker numbering.
         */


        for (seq_no = 1; seq_no <= MAX_SEQ_NO; seq_no++)
                marker_present[seq_no] = 0;
        seq_no = 0;
        for (marker = cinfo->marker_list; marker != NULL; marker = marker->next)
        {
                if (requestmarker == ICC_MARKER && marker_is_icc(marker))
                {
                        if (num_markers == 0)
                                num_markers = GETJOCTET(marker->data[13]);
                        else if (num_markers != GETJOCTET(marker->data[13]))
                                return false;           /* inconsistent num_markers fields */
                        seq_no = GETJOCTET(marker->data[12]);
                        if (seq_no <= 0 || seq_no > num_markers)
                                return false;           /* bogus sequence number */
                        if (marker_present[seq_no])
                                return false;           /* duplicate sequence numbers */
                        marker_present[seq_no] = 1;
                        data_length[seq_no] = marker->data_length - ICC_OVERHEAD_LEN;
                }
                else if(requestmarker == PHOTOSHOP_MARKER && marker_is_photoshop(marker))
                {
                        num_markers = ++seq_no;
                        marker_present[seq_no] = 1;
                        data_length[seq_no] = marker->data_length - ICC_OVERHEAD_LEN;
                }
        }

        if (num_markers == 0)
                return false;

        /* Check for missing markers, count total space needed,
         * compute offset of each marker's part of the data.
         */


        total_length = 0;
        for (seq_no = 1; seq_no <= num_markers; seq_no++)
        {
                if (marker_present[seq_no] == 0)
                        return false;           /* missing sequence number */
                data_offset[seq_no] = total_length;
                total_length += data_length[seq_no];
        }

        if (total_length <= 0)
                return false;           /* found only empty markers? */

        /* Allocate space for assembled data */
        icc_data = (JOCTET *) malloc(total_length * sizeof(JOCTET));
        if (icc_data == NULL)
                return false;           /* oops, out of memory */
        seq_no=0;
        /* and fill it in */
        for (marker = cinfo->marker_list; marker != NULL; marker = marker->next)
        {
                if ( (requestmarker == ICC_MARKER && marker_is_icc(marker)) ||
                        (requestmarker == PHOTOSHOP_MARKER && marker_is_photoshop(marker)) || (requestmarker == 0xE1))
                {
                        JOCTET FAR *src_ptr;
                        JOCTET *dst_ptr;
                        unsigned int length;
                        if(requestmarker == ICC_MARKER)
                                seq_no = GETJOCTET(marker->data[12]);
                        else if(requestmarker == PHOTOSHOP_MARKER)
                                seq_no++;
                        dst_ptr = icc_data + data_offset[seq_no];
                        src_ptr = marker->data + ICC_OVERHEAD_LEN;
                        length = data_length[seq_no];
                        while (length--)
                        {
                                *dst_ptr++ = *src_ptr++;
                        }
                }
        }

        *icc_data_ptr = icc_data;
        *icc_data_len = total_length;

        return true;
}