File indexing completed on 2024-05-26 04:33:41

0001 /*
0002  *  SPDX-FileCopyrightText: 2005 Cyrille Berger <cberger@cberger.net>
0003  *  SPDX-FileCopyrightText: 2022 L. E. Segovia <amy@amyspark.me>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "kis_tiff_export.h"
0009 
0010 #include <QBuffer>
0011 #include <QFileInfo>
0012 
0013 #include <memory>
0014 
0015 #include <exiv2/exiv2.hpp>
0016 #include <kpluginfactory.h>
0017 #ifdef Q_OS_WIN
0018 #include <io.h>
0019 #endif
0020 #include <tiffio.h>
0021 
0022 #include <KisDocument.h>
0023 #include <KisExportCheckRegistry.h>
0024 #include <KoColorModelStandardIds.h>
0025 #include <KoDocumentInfo.h>
0026 #include <KoUnit.h>
0027 #include <kis_assert.h>
0028 #include <kis_group_layer.h>
0029 #include <kis_layer_utils.h>
0030 #include <kis_meta_data_backend_registry.h>
0031 #include <kis_paint_layer.h>
0032 #include <kis_tiff_writer_visitor.h>
0033 #include <KisExiv2IODevice.h>
0034 
0035 #include <config-tiff.h>
0036 #ifdef TIFF_CAN_WRITE_PSD_TAGS
0037 #include "kis_tiff_psd_writer_visitor.h"
0038 #endif
0039 
0040 #include "kis_dlg_options_tiff.h"
0041 #include "kis_tiff_converter.h"
0042 #include "kis_tiff_logger.h"
0043 
0044 K_PLUGIN_FACTORY_WITH_JSON(KisTIFFExportFactory, "krita_tiff_export.json", registerPlugin<KisTIFFExport>();)
0045 
0046 KisTIFFExport::KisTIFFExport(QObject *parent, const QVariantList &)
0047     : KisImportExportFilter(parent)
0048     , oldErrHandler(TIFFSetErrorHandler(&KisTiffErrorHandler))
0049     , oldWarnHandler(TIFFSetWarningHandler(&KisTiffWarningHandler))
0050 {
0051 }
0052 
0053 KisTIFFExport::~KisTIFFExport()
0054 {
0055     TIFFSetErrorHandler(oldErrHandler);
0056     TIFFSetWarningHandler(oldWarnHandler);
0057 }
0058 
0059 KisImportExportErrorCode KisTIFFExport::convert(KisDocument *document, QIODevice */*io*/,  KisPropertiesConfigurationSP configuration)
0060 {
0061     // If a configuration object was passed to the convert method, we use that, otherwise we load from the settings
0062     KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration());
0063     if (configuration) {
0064         cfg->fromXML(configuration->toXML());
0065     }
0066     else {
0067         cfg = lastSavedConfiguration(KisDocument::nativeFormatMimeType(), "image/tiff");
0068     }
0069 
0070     KisTIFFOptions options;
0071     options.fromProperties(configuration);
0072 
0073     if (!options.flatten && !options.saveAsPhotoshop) {
0074         const bool hasGroupLayers =
0075             KisLayerUtils::recursiveFindNode(document->savingImage()->root(),
0076                 [] (KisNodeSP node) {
0077                     return node->parent() && node->inherits("KisGroupLayer");
0078                 });
0079         options.flatten = hasGroupLayers;
0080     }
0081 
0082     KisImageSP kisimage = [&]() {
0083         if (options.flatten) {
0084             KisImageSP image =
0085                 new KisImage(nullptr,
0086                              document->savingImage()->width(),
0087                              document->savingImage()->height(),
0088                              document->savingImage()->colorSpace(),
0089                              "");
0090             image->setResolution(document->savingImage()->xRes(),
0091                                  document->savingImage()->yRes());
0092             KisPaintDeviceSP pd = KisPaintDeviceSP(
0093                 new KisPaintDevice(*document->savingImage()->projection()));
0094             KisPaintLayerSP l =
0095                 KisPaintLayerSP(new KisPaintLayer(image.data(),
0096                                                   "projection",
0097                                                   OPACITY_OPAQUE_U8,
0098                                                   pd));
0099             image->addNode(KisNodeSP(l.data()), image->rootLayer().data());
0100             return image;
0101         } else {
0102             return document->savingImage();
0103         }
0104     }();
0105 
0106     dbgFile << "Start writing TIFF File";
0107     KIS_ASSERT_RECOVER_RETURN_VALUE(kisimage, ImportExportCodes::InternalError);
0108 
0109     QFile file(filename());
0110     if (!file.open(QFile::ReadWrite)) {
0111         return {KisImportExportErrorCannotRead(file.error())};
0112     }
0113 
0114     // Open file for writing
0115     const QByteArray encodedFilename = QFile::encodeName(filename());
0116 
0117     // https://gitlab.com/libtiff/libtiff/-/issues/173
0118 #ifdef Q_OS_WIN
0119     const int handle = (int)(_get_osfhandle(file.handle()));
0120 #else
0121     const int handle = file.handle();
0122 #endif
0123 
0124     // NOLINTNEXTLINE(bugprone-narrowing-conversions, cppcoreguidelines-narrowing-conversions)
0125     std::unique_ptr<TIFF, decltype(&TIFFCleanup)> image(TIFFFdOpen(handle, encodedFilename.data(), "w"), &TIFFCleanup);
0126 
0127     if (!image) {
0128         dbgFile << "Could not open the file for writing" << filename();
0129         return ImportExportCodes::NoAccessToWrite;
0130     }
0131 
0132     // Set the document information
0133     KoDocumentInfo *info = document->documentInfo();
0134     QString title = info->aboutInfo("title");
0135     if (!title.isEmpty()) {
0136         if (!TIFFSetField(image.get(),
0137                           TIFFTAG_DOCUMENTNAME,
0138                           title.toLatin1().constData())) {
0139             return ImportExportCodes::ErrorWhileWriting;
0140         }
0141     }
0142     QString abstract = info->aboutInfo("description");
0143     if (!abstract.isEmpty()) {
0144         if (!TIFFSetField(image.get(),
0145                           TIFFTAG_IMAGEDESCRIPTION,
0146                           abstract.toLatin1().constData())) {
0147             return ImportExportCodes::ErrorWhileWriting;
0148         }
0149     }
0150     QString author = info->authorInfo("creator");
0151     if (!author.isEmpty()) {
0152         if (!TIFFSetField(image.get(),
0153                           TIFFTAG_ARTIST,
0154                           author.toLatin1().constData())) {
0155             return ImportExportCodes::ErrorWhileWriting;
0156         }
0157     }
0158 
0159     dbgFile << "xres: " << INCH_TO_POINT(kisimage->xRes())
0160             << " yres: " << INCH_TO_POINT(kisimage->yRes());
0161     if (!TIFFSetField(
0162             image.get(),
0163             TIFFTAG_XRESOLUTION,
0164             INCH_TO_POINT(kisimage->xRes()))) { // It is the "invert" macro
0165                                                 // because we convert from
0166                                                 // pointer-per-inch to points
0167         return ImportExportCodes::ErrorWhileWriting;
0168     }
0169     if (!TIFFSetField(image.get(),
0170                       TIFFTAG_YRESOLUTION,
0171                       INCH_TO_POINT(kisimage->yRes()))) {
0172         return ImportExportCodes::ErrorWhileWriting;
0173     }
0174 
0175     if (!TIFFSetField(image.get(), TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH)) {
0176         return ImportExportCodes::ErrorWhileWriting;
0177     }
0178 
0179     KisGroupLayer *root =
0180         dynamic_cast<KisGroupLayer *>(kisimage->rootLayer().data());
0181     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(root,
0182                                          ImportExportCodes::InternalError);
0183 
0184 #ifdef TIFF_CAN_WRITE_PSD_TAGS
0185     if (options.saveAsPhotoshop) {
0186         KisTiffPsdWriter writer(image.get(), &options);
0187         KisImportExportErrorCode result = writer.writeImage(root);
0188         if (!result.isOk()) {
0189             return result;
0190         }
0191     } else
0192 #endif // TIFF_CAN_WRITE_PSD_TAGS
0193     {
0194         KisTIFFWriterVisitor visitor(image.get(), &options);
0195         if (!(visitor.visit(root))) {
0196             return ImportExportCodes::Failure;
0197         }
0198     }
0199 
0200     image.reset();
0201     file.close();
0202 
0203     if (!options.flatten && !options.saveAsPhotoshop) {
0204         // HACK!! Externally inject the Exif metadata
0205         // libtiff has no way to access the fields wholesale
0206         try {
0207             KisExiv2IODevice::ptr_type basicIoDevice(new KisExiv2IODevice(filename()));
0208 
0209 #if EXIV2_TEST_VERSION(0,28,0)
0210             const std::unique_ptr<Exiv2::Image> img = Exiv2::ImageFactory::open(std::move(basicIoDevice));
0211 #else
0212             const std::unique_ptr<Exiv2::Image> img(Exiv2::ImageFactory::open(basicIoDevice).release());
0213 #endif
0214 
0215             img->readMetadata();
0216 
0217             Exiv2::ExifData &data = img->exifData();
0218 
0219             const KisMetaData::IOBackend *io =
0220                 KisMetadataBackendRegistry::instance()->value("exif");
0221 
0222             // All IFDs are paint layer children of root
0223             KisNodeSP node = root->firstChild();
0224 
0225             QBuffer ioDevice;
0226 
0227             // Get layer
0228             KisLayer *layer = qobject_cast<KisLayer *>(node.data());
0229             Q_ASSERT(layer);
0230 
0231             // Inject the data as any other IOBackend
0232             io->saveTo(layer->metaData(), &ioDevice);
0233 
0234             Exiv2::ExifData dataToInject;
0235 
0236             // Reinterpret the blob we just got and inject its contents into
0237             // tempData
0238             Exiv2::ExifParser::decode(
0239                 dataToInject,
0240                 reinterpret_cast<const Exiv2::byte *>(ioDevice.data().data()),
0241                 static_cast<uint32_t>(ioDevice.size()));
0242 
0243             for (const auto &v : dataToInject) {
0244                 data[v.key()] = v.value();
0245             }
0246             // Write metadata
0247             img->writeMetadata();
0248 #if EXIV2_TEST_VERSION(0,28,0)
0249         } catch (Exiv2::Error &e) {
0250             errFile << "Failed injecting TIFF metadata:" << Exiv2::Error(e.code()).what();
0251 #else
0252         } catch (Exiv2::AnyError &e) {
0253             errFile << "Failed injecting TIFF metadata:" << e.code()
0254                     << e.what();
0255 #endif
0256         }
0257     }
0258     return ImportExportCodes::OK;
0259 }
0260 
0261 KisPropertiesConfigurationSP KisTIFFExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const
0262 {
0263     KisTIFFOptions options;
0264     return options.toProperties();
0265 }
0266 
0267 KisConfigWidget *KisTIFFExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const
0268 {
0269     return new KisTIFFOptionsWidget(parent);
0270 }
0271 
0272 void KisTIFFExport::initializeCapabilities()
0273 {
0274     addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED));
0275     addCapability(KisExportCheckRegistry::instance()->get("LayerOpacityCheck")->create(KisExportCheckBase::PARTIALLY));
0276     addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED));
0277     addCapability(KisExportCheckRegistry::instance()->get("ExifCheck")->create(KisExportCheckBase::SUPPORTED));
0278     addCapability(KisExportCheckRegistry::instance()
0279                       ->get("TiffExifCheck")
0280                       ->create(KisExportCheckBase::PARTIALLY));
0281     addCapability(
0282         KisExportCheckRegistry::instance()->get("ColorModelHomogenousCheck")->create(KisExportCheckBase::SUPPORTED));
0283 
0284     QList<QPair<KoID, KoID>> supportedColorModels = {
0285         {},
0286         {RGBAColorModelID, Integer8BitsColorDepthID},
0287         {RGBAColorModelID, Integer16BitsColorDepthID},
0288         {RGBAColorModelID, Float16BitsColorDepthID},
0289         {RGBAColorModelID, Float32BitsColorDepthID},
0290         {GrayAColorModelID, Integer8BitsColorDepthID},
0291         {GrayAColorModelID, Integer16BitsColorDepthID},
0292         {CMYKAColorModelID, Integer8BitsColorDepthID},
0293         {CMYKAColorModelID, Integer16BitsColorDepthID},
0294         {YCbCrAColorModelID, Integer8BitsColorDepthID},
0295         {YCbCrAColorModelID, Integer16BitsColorDepthID},
0296         {YCbCrAColorModelID, Float16BitsColorDepthID},
0297         {YCbCrAColorModelID, Float32BitsColorDepthID},
0298         {LABAColorModelID, Integer8BitsColorDepthID},
0299         {LABAColorModelID, Integer16BitsColorDepthID},
0300         {LABAColorModelID, Float16BitsColorDepthID},
0301         {LABAColorModelID, Float32BitsColorDepthID}};
0302     addSupportedColorModels(supportedColorModels, "TIFF");
0303 
0304 }
0305 
0306 #include <kis_tiff_export.moc>
0307