File indexing completed on 2024-05-12 04:33:19

0001 /*
0002     SPDX-FileCopyrightText: 2007-2018 Gilles Caulier <caulier dot gilles at gmail dot com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "kipiwriteimage.h"
0008 #include "kipiwritehelp.h"
0009 
0010 // C ANSI includes
0011 
0012 extern "C"
0013 {
0014 #ifndef _WIN32
0015 #include <unistd.h>
0016 #include <sys/ipc.h>
0017 #include <sys/shm.h>
0018 #endif
0019 #include <sys/types.h>
0020 #include <tiffvers.h>
0021 }
0022 
0023 // Qt includes
0024 
0025 #include <QFile>
0026 #include <QDebug>
0027 #include <QDataStream>
0028 #include <QStandardPaths>
0029 
0030 namespace KXMLKipiCmd
0031 {
0032 
0033 class KIPIWriteImage::Private
0034 {
0035 public:
0036 
0037     Private()
0038     {
0039         sixteenBit     = false;
0040         hasAlpha       = false;
0041         width          = 0;
0042         height         = 0;
0043         cancel         = nullptr;
0044     }
0045 
0046     bool*      cancel;
0047 
0048     bool       sixteenBit;
0049     bool       hasAlpha;
0050 
0051     uint       width;
0052     uint       height;
0053 
0054     QByteArray data;         // BGR(A) image data
0055                              // data[0] = blue, data[1] = green, data[2] = red, data[3] = alpha.
0056 };
0057 
0058 KIPIWriteImage::KIPIWriteImage()
0059     : d(new Private)
0060 {
0061 }
0062 
0063 KIPIWriteImage::~KIPIWriteImage()
0064 {
0065     delete d;
0066 }
0067 
0068 int KIPIWriteImage::bytesDepth() const
0069 {
0070     if (d->sixteenBit)
0071     {
0072         if (d->hasAlpha)
0073             return 8;
0074         else
0075             return 6;
0076     }
0077 
0078     if (d->hasAlpha)
0079         return 4;
0080 
0081     return 3;
0082 }
0083 
0084 void KIPIWriteImage::setCancel(bool* const cancel)
0085 {
0086     d->cancel = cancel;
0087 }
0088 
0089 bool KIPIWriteImage::cancel() const
0090 {
0091     if (d->cancel)
0092         return *d->cancel;
0093 
0094     return false;
0095 }
0096 
0097 void KIPIWriteImage::setImageData(const QByteArray& data, uint width, uint height,
0098                                 bool  sixteenBit, bool hasAlpha)
0099 {
0100     d->data       = data;
0101     d->width      = width;
0102     d->height     = height;
0103     d->sixteenBit = sixteenBit;
0104     d->hasAlpha   = hasAlpha;
0105 }
0106 
0107 bool KIPIWriteImage::write2JPEG(const QString& destPath)
0108 {
0109     QFile file(destPath);
0110 
0111     if (!file.open(QIODevice::ReadWrite))
0112     {
0113         qDebug() << "Failed to open JPEG file for writing" ;
0114         return false;
0115     }
0116 
0117     struct jpeg_compress_struct cinfo;
0118     struct jpeg_error_mgr       jerr;
0119 
0120     // Init JPEG compressor.
0121     cinfo.err              = jpeg_std_error(&jerr);
0122     jpeg_create_compress(&cinfo);
0123     kipi_jpeg_qiodevice_dest(&cinfo, &file);
0124     cinfo.image_width      = d->width;
0125     cinfo.image_height     = d->height;
0126     cinfo.input_components = 3;
0127     cinfo.in_color_space   = JCS_RGB;
0128     jpeg_set_defaults(&cinfo);
0129 
0130     // bug #149578: set encoder horizontal and vertical chroma subsampling
0131     // factor to 2x1, 1x1, 1x1 (4:2:2) : Medium subsampling.
0132     // See this page for details: http://en.wikipedia.org/wiki/Chroma_subsampling
0133     cinfo.comp_info[0].h_samp_factor = 2;
0134     cinfo.comp_info[0].v_samp_factor = 1;
0135     cinfo.comp_info[1].h_samp_factor = 1;
0136     cinfo.comp_info[1].v_samp_factor = 1;
0137     cinfo.comp_info[2].h_samp_factor = 1;
0138     cinfo.comp_info[2].v_samp_factor = 1;
0139 
0140     // bug #154273: use 99 compression level instead 100 to reduce output JPEG file size.
0141     jpeg_set_quality(&cinfo, 99, boolean(true));
0142     jpeg_start_compress(&cinfo, boolean(true));
0143 
0144     // Write image data
0145     uchar* line   = new uchar[d->width*3];
0146     uchar* dstPtr = nullptr;
0147 
0148     if (!d->sixteenBit)     // 8 bits image.
0149     {
0150         uchar* srcPtr = (uchar*)d->data.data();
0151 
0152         for (uint j=0; j < d->height; ++j)
0153         {
0154             if (cancel())
0155             {
0156                 delete [] line;
0157                 jpeg_destroy_compress(&cinfo);
0158                 file.close();
0159                 return false;
0160             }
0161 
0162             dstPtr = line;
0163 
0164             for (uint i = 0; i < d->width; ++i)
0165             {
0166                 dstPtr[2] = srcPtr[0];  // Blue
0167                 dstPtr[1] = srcPtr[1];  // Green
0168                 dstPtr[0] = srcPtr[2];  // Red
0169 
0170                 d->hasAlpha ? srcPtr += 4 : srcPtr += 3;
0171                 dstPtr += 3;
0172             }
0173 
0174             jpeg_write_scanlines(&cinfo, &line, 1);
0175         }
0176     }
0177     else                    // 16 bits image
0178     {
0179         unsigned short* srcPtr = reinterpret_cast<unsigned short*>(d->data.data());
0180 
0181         for (uint j=0; j < d->height; ++j)
0182         {
0183             if (cancel())
0184             {
0185                 delete [] line;
0186                 jpeg_destroy_compress(&cinfo);
0187                 file.close();
0188                 return false;
0189             }
0190 
0191             dstPtr = line;
0192 
0193             for (uint i = 0; i < d->width; ++i)
0194             {
0195                 dstPtr[2] = (srcPtr[0] * 255UL)/65535UL;    // Blue
0196                 dstPtr[1] = (srcPtr[1] * 255UL)/65535UL;    // Green
0197                 dstPtr[0] = (srcPtr[2] * 255UL)/65535UL;    // Red
0198 
0199                 d->hasAlpha ? srcPtr += 4 : srcPtr += 3;
0200                 dstPtr += 3;
0201             }
0202 
0203             jpeg_write_scanlines(&cinfo, &line, 1);
0204         }
0205     }
0206 
0207     delete [] line;
0208 
0209     jpeg_finish_compress(&cinfo);
0210     jpeg_destroy_compress(&cinfo);
0211     file.close();
0212 
0213     return true;
0214 }
0215 
0216 bool KIPIWriteImage::write2PPM(const QString& destPath)
0217 {
0218     FILE* const file = fopen((const char*)(QFile::encodeName(destPath)).constData(), "wb");
0219 
0220     if (!file)
0221     {
0222         qDebug() << "Failed to open ppm file for writing" ;
0223         return false;
0224     }
0225 
0226     fprintf(file, "P6\n%d %d\n255\n", d->width, d->height);
0227 
0228     // Write image data
0229     uchar* const line = new uchar[d->width*3];
0230     uchar* dstPtr     = nullptr;
0231 
0232     if (!d->sixteenBit)     // 8 bits image.
0233     {
0234         uchar* srcPtr = (uchar*)d->data.data();
0235 
0236         for (uint j=0; j < d->height; ++j)
0237         {
0238             if (cancel())
0239             {
0240                 delete [] line;
0241                 fclose(file);
0242                 return false;
0243             }
0244 
0245             dstPtr = line;
0246 
0247             for (uint i = 0; i < d->width; ++i)
0248             {
0249                 dstPtr[2] = srcPtr[0];  // Blue
0250                 dstPtr[1] = srcPtr[1];  // Green
0251                 dstPtr[0] = srcPtr[2];  // Red
0252 
0253                 d->hasAlpha ? srcPtr += 4 : srcPtr += 3;
0254                 dstPtr += 3;
0255             }
0256 
0257             fwrite(line, 1, d->width*3, file);
0258         }
0259     }
0260     else                    // 16 bits image
0261     {
0262         unsigned short* srcPtr = reinterpret_cast<unsigned short*>(d->data.data());
0263 
0264         for (uint j=0; j < d->height; ++j)
0265         {
0266             if (cancel())
0267             {
0268                 delete [] line;
0269                 fclose(file);
0270                 return false;
0271             }
0272 
0273             dstPtr = line;
0274 
0275             for (uint i = 0; i < d->width; ++i)
0276             {
0277                 dstPtr[2] = (srcPtr[0] * 255UL)/65535UL;    // Blue
0278                 dstPtr[1] = (srcPtr[1] * 255UL)/65535UL;    // Green
0279                 dstPtr[0] = (srcPtr[2] * 255UL)/65535UL;    // Red
0280 
0281                 d->hasAlpha ? srcPtr += 4 : srcPtr += 3;
0282                 dstPtr += 3;
0283             }
0284 
0285             fwrite(line, 1, d->width*3, file);
0286         }
0287     }
0288 
0289     delete [] line;
0290     fclose(file);
0291 
0292     return true;
0293 }
0294 
0295 bool KIPIWriteImage::write2PNG(const QString& destPath)
0296 {
0297     /*
0298     check this out for b/w support:
0299     http://lxr.kde.org/source/playground/graphics/krita-exp/kis_png_converter.cpp#607
0300     */
0301     QFile file(destPath);
0302 
0303     if (!file.open(QIODevice::ReadWrite))
0304     {
0305         qDebug() << "Failed to open PNG file for writing" ;
0306         return false;
0307     }
0308 
0309     uchar*       data       = nullptr;
0310     int          bitsDepth  = d->sixteenBit ? 16 : 8;
0311     png_color_8  sig_bit;
0312     png_bytep    row_ptr;
0313     png_structp  png_ptr    = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
0314     png_infop    info_ptr   = png_create_info_struct(png_ptr);
0315 
0316     png_set_write_fn(png_ptr, (void*)&file, kipi_png_write_fn, kipi_png_flush_fn);
0317 
0318     if (QSysInfo::ByteOrder == QSysInfo::LittleEndian)      // Intel
0319         png_set_bgr(png_ptr);
0320     else                                                    // PPC
0321         png_set_swap_alpha(png_ptr);
0322 
0323     if (d->hasAlpha)
0324     {
0325         png_set_IHDR(png_ptr, info_ptr, d->width, d->height, bitsDepth,
0326                      PNG_COLOR_TYPE_RGB_ALPHA,  PNG_INTERLACE_NONE,
0327                      PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
0328 
0329         if (d->sixteenBit)
0330             data = new uchar[d->width * 8 * sizeof(uchar)];
0331         else
0332             data = new uchar[d->width * 4 * sizeof(uchar)];
0333     }
0334     else
0335     {
0336         png_set_IHDR(png_ptr, info_ptr, d->width, d->height, bitsDepth,
0337                      PNG_COLOR_TYPE_RGB,        PNG_INTERLACE_NONE,
0338                      PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
0339 
0340         if (d->sixteenBit)
0341             data = new uchar[d->width * 6 * sizeof(uchar)];
0342         else
0343             data = new uchar[d->width * 3 * sizeof(uchar)];
0344     }
0345 
0346     sig_bit.red   = bitsDepth;
0347     sig_bit.green = bitsDepth;
0348     sig_bit.blue  = bitsDepth;
0349     sig_bit.alpha = bitsDepth;
0350     png_set_sBIT(png_ptr, info_ptr, &sig_bit);
0351     png_set_compression_level(png_ptr, 9);
0352 
0353     // Write Software info.
0354     QString libpngver(QLatin1String(PNG_HEADER_VERSION_STRING));
0355     libpngver.replace(QLatin1Char('\n'), QLatin1Char(' '));
0356     QByteArray softAscii = libpngver.toLatin1();
0357     png_text text;
0358     text.key         = (png_charp)"Software";
0359     text.text        = softAscii.data();
0360     text.compression = PNG_TEXT_COMPRESSION_zTXt;
0361     png_set_text(png_ptr, info_ptr, &(text), 1);
0362 
0363     png_write_info(png_ptr, info_ptr);
0364     png_set_shift(png_ptr, &sig_bit);
0365     png_set_packing(png_ptr);
0366 
0367     uchar* ptr = (uchar*)d->data.data();
0368     uint   x, y, j;
0369 
0370     for (y = 0; y < d->height; ++y)
0371     {
0372         if (cancel())
0373         {
0374             delete [] data;
0375             file.close();
0376             png_destroy_write_struct(&png_ptr, (png_infopp) & info_ptr);
0377             png_destroy_info_struct(png_ptr, (png_infopp) & info_ptr);
0378             return false;
0379         }
0380 
0381         j = 0;
0382 
0383         for (x = 0; x < d->width*bytesDepth(); x+=bytesDepth())
0384         {
0385             if (d->sixteenBit)
0386             {
0387                 if (d->hasAlpha)
0388                 {
0389                     data[j++] = ptr[x+1];  // Blue
0390                     data[j++] = ptr[ x ];
0391                     data[j++] = ptr[x+3];  // Green
0392                     data[j++] = ptr[x+2];
0393                     data[j++] = ptr[x+5];  // Red
0394                     data[j++] = ptr[x+4];
0395                     data[j++] = ptr[x+7];  // Alpha
0396                     data[j++] = ptr[x+6];
0397                 }
0398                 else
0399                 {
0400                     data[j++] = ptr[x+1];  // Blue
0401                     data[j++] = ptr[ x ];
0402                     data[j++] = ptr[x+3];  // Green
0403                     data[j++] = ptr[x+2];
0404                     data[j++] = ptr[x+5];  // Red
0405                     data[j++] = ptr[x+4];
0406                 }
0407             }
0408             else
0409             {
0410                 if (d->hasAlpha)
0411                 {
0412                     data[j++] = ptr[ x ];  // Blue
0413                     data[j++] = ptr[x+1];  // Green
0414                     data[j++] = ptr[x+2];  // Red
0415                     data[j++] = ptr[x+3];  // Alpha
0416                 }
0417                 else
0418                 {
0419                     data[j++] = ptr[ x ];  // Blue
0420                     data[j++] = ptr[x+1];  // Green
0421                     data[j++] = ptr[x+2];  // Red
0422                 }
0423             }
0424         }
0425 
0426         row_ptr = (png_bytep) data;
0427 
0428         png_write_rows(png_ptr, &row_ptr, 1);
0429         ptr += (d->width * bytesDepth());
0430     }
0431 
0432     delete [] data;
0433 
0434     png_write_end(png_ptr, info_ptr);
0435     png_destroy_write_struct(&png_ptr, (png_infopp) & info_ptr);
0436     png_destroy_info_struct(png_ptr, (png_infopp) & info_ptr);
0437     file.close();
0438 
0439     return true;
0440 }
0441 
0442 bool KIPIWriteImage::write2TIFF(const QString& destPath)
0443 {
0444     uint32 w          = d->width;
0445     uint32 h          = d->height;
0446     uchar* const data = (uchar*)d->data.data();
0447 
0448     // TIFF error handling. If an errors/warnings occurs during reading,
0449     // libtiff will call these methods
0450 
0451     TIFFSetWarningHandler(kipi_tiff_warning);
0452     TIFFSetErrorHandler(kipi_tiff_error);
0453 
0454     // Open target file
0455 
0456     TIFF* const tif = TIFFOpen((const char*)(QFile::encodeName(destPath)).constData(), "w");
0457 
0458     if (!tif)
0459     {
0460         qDebug() << "Failed to open TIFF file for writing" ;
0461         return false;
0462     }
0463 
0464     int bitsDepth = d->sixteenBit ? 16 : 8;
0465 
0466     TIFFSetField(tif, TIFFTAG_IMAGEWIDTH,          w);
0467     TIFFSetField(tif, TIFFTAG_IMAGELENGTH,         h);
0468     TIFFSetField(tif, TIFFTAG_PHOTOMETRIC,         PHOTOMETRIC_RGB);
0469     TIFFSetField(tif, TIFFTAG_PLANARCONFIG,        PLANARCONFIG_CONTIG);
0470     TIFFSetField(tif, TIFFTAG_ORIENTATION,         ORIENTATION_TOPLEFT);
0471     TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT,      RESUNIT_NONE);
0472     TIFFSetField(tif, TIFFTAG_COMPRESSION,         COMPRESSION_ADOBE_DEFLATE);
0473     TIFFSetField(tif, TIFFTAG_ZIPQUALITY,          9);
0474     // NOTE : this tag values aren't defined in libtiff 3.6.1. '2' is PREDICTOR_HORIZONTAL.
0475     //        Use horizontal differencing for images which are
0476     //        likely to be continuous tone. The TIFF spec says that this
0477     //        usually leads to better compression.
0478     //        See this url for more details:
0479     //        http://www.awaresystems.be/imaging/tiff/tifftags/predictor.html
0480     TIFFSetField(tif, TIFFTAG_PREDICTOR,           2);
0481 
0482     uint16 sampleinfo[1];
0483 
0484     if (d->hasAlpha)
0485     {
0486         sampleinfo[0] = EXTRASAMPLE_ASSOCALPHA;
0487         TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 4);
0488         TIFFSetField(tif, TIFFTAG_EXTRASAMPLES,    1, sampleinfo);
0489     }
0490     else
0491     {
0492         TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3);
0493     }
0494 
0495     TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE,       (uint16)bitsDepth);
0496     TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP,        TIFFDefaultStripSize(tif, 0));
0497 
0498     QString libtiffver(QLatin1String(TIFFLIB_VERSION_STR));
0499     libtiffver.replace(QLatin1Char('\n'), QLatin1Char(' '));
0500     TIFFSetField(tif, TIFFTAG_SOFTWARE, (const char*)libtiffver.toLatin1().data());
0501 
0502     // Write full image data in tiff directory IFD0
0503 
0504     uchar*  pixel = nullptr;
0505     double  alpha_factor;
0506     uint32  x, y;
0507     uint8   r8, g8, b8, a8=0;
0508     uint16  r16, g16, b16, a16=0;
0509     int     i=0;
0510 
0511     uint8* const buf = (uint8 *)_TIFFmalloc(TIFFScanlineSize(tif));
0512 
0513     if (!buf)
0514     {
0515         qDebug() << "Cannot allocate memory buffer for main TIFF image." ;
0516         TIFFClose(tif);
0517         return false;
0518     }
0519 
0520     for (y = 0; y < h; ++y)
0521     {
0522         if (cancel())
0523         {
0524             _TIFFfree(buf);
0525             TIFFClose(tif);
0526             return false;
0527         }
0528 
0529         i = 0;
0530 
0531         for (x = 0; x < w; ++x)
0532         {
0533             pixel = &data[((y * w) + x) * bytesDepth()];
0534 
0535             if ( d->sixteenBit )        // 16 bits image.
0536             {
0537                 b16 = (uint16)(pixel[0]+256*pixel[1]);
0538                 g16 = (uint16)(pixel[2]+256*pixel[3]);
0539                 r16 = (uint16)(pixel[4]+256*pixel[5]);
0540 
0541                 if (d->hasAlpha)
0542                 {
0543                     // TIFF makes you pre-multiply the rgb components by alpha
0544 
0545                     a16          = (uint16)(pixel[6]+256*pixel[7]);
0546                     alpha_factor = ((double)a16 / 65535.0);
0547                     r16          = (uint16)(r16*alpha_factor);
0548                     g16          = (uint16)(g16*alpha_factor);
0549                     b16          = (uint16)(b16*alpha_factor);
0550                 }
0551 
0552                 // This might be endian dependent
0553 
0554                 buf[i++] = (uint8)(r16);
0555                 buf[i++] = (uint8)(r16 >> 8);
0556                 buf[i++] = (uint8)(g16);
0557                 buf[i++] = (uint8)(g16 >> 8);
0558                 buf[i++] = (uint8)(b16);
0559                 buf[i++] = (uint8)(b16 >> 8);
0560 
0561                 if (d->hasAlpha)
0562                 {
0563                     buf[i++] = (uint8)(a16) ;
0564                     buf[i++] = (uint8)(a16 >> 8) ;
0565                 }
0566             }
0567             else                            // 8 bits image.
0568             {
0569                 b8 = (uint8)pixel[0];
0570                 g8 = (uint8)pixel[1];
0571                 r8 = (uint8)pixel[2];
0572 
0573                 if (d->hasAlpha)
0574                 {
0575                     // TIFF makes you pre-multiply the rgb components by alpha
0576 
0577                     a8           = (uint8)(pixel[3]);
0578                     alpha_factor = ((double)a8 / 255.0);
0579                     r8           = (uint8)(r8*alpha_factor);
0580                     g8           = (uint8)(g8*alpha_factor);
0581                     b8           = (uint8)(b8*alpha_factor);
0582                 }
0583 
0584                 // This might be endian dependent
0585 
0586                 buf[i++] = r8;
0587                 buf[i++] = g8;
0588                 buf[i++] = b8;
0589 
0590                 if (d->hasAlpha)
0591                     buf[i++] = a8;
0592             }
0593         }
0594 
0595         if (!TIFFWriteScanline(tif, buf, y, 0))
0596         {
0597             qDebug() << "Cannot write main TIFF image to target file." ;
0598             _TIFFfree(buf);
0599             TIFFClose(tif);
0600             return false;
0601         }
0602     }
0603 
0604     _TIFFfree(buf);
0605     TIFFWriteDirectory(tif);
0606     TIFFClose(tif);
0607 
0608     return true;
0609 }
0610 
0611 }  // namespace KXMLKipiCmd