File indexing completed on 2024-04-21 15:08:49

0001 /***************************************************************************
0002  *   Copyright (C) 2012-2016 by Daniel Nicoletti <dantti12@gmail.com>      *
0003  *                                                                         *
0004  *   This program is free software; you can redistribute it and/or modify  *
0005  *   it under the terms of the GNU General Public License as published by  *
0006  *   the Free Software Foundation; either version 2 of the License, or     *
0007  *   (at your option) any later version.                                   *
0008  *                                                                         *
0009  *   This program is distributed in the hope that it will be useful,       *
0010  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0011  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0012  *   GNU General Public License for more details.                          *
0013  *                                                                         *
0014  *   You should have received a copy of the GNU General Public License     *
0015  *   along with this program; see the file COPYING. If not, write to       *
0016  *   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,  *
0017  *   Boston, MA 02110-1301, USA.                                           *
0018  ***************************************************************************/
0019 
0020 #include "Profile.h"
0021 
0022 #include <QCryptographicHash>
0023 #include <QDebug>
0024 #include <QFile>
0025 #include <QLocale>
0026 
0027 #include <KLocalizedString>
0028 
0029 Profile::Profile(const QString &filename)
0030 {
0031     setFilename(filename);
0032 }
0033 
0034 Profile::~Profile()
0035 {
0036     if (m_lcmsProfile) {
0037         cmsCloseProfile(m_lcmsProfile);
0038     }
0039 }
0040 
0041 void Profile::setFilename(const QString &filename)
0042 {
0043     if (!filename.isEmpty()) {
0044         m_filename = filename;
0045         QFile file(filename);
0046         if (file.open(QIODevice::ReadOnly)) {
0047             QByteArray data;
0048             data = file.readAll();
0049             parseProfile((const uint *)data.data(), data.size());
0050         }
0051     }
0052 }
0053 
0054 QString Profile::errorMessage() const
0055 {
0056     return m_errorMessage;
0057 }
0058 
0059 QColor Profile::convertXYZ(cmsCIEXYZ *cieXYZ)
0060 {
0061     typedef struct {
0062         quint8 R;
0063         quint8 G;
0064         quint8 B;
0065     } CdColorRGB8;
0066     QColor ret;
0067     CdColorRGB8 rgb;
0068     cmsHPROFILE profile_srgb = nullptr;
0069     cmsHPROFILE profile_xyz = nullptr;
0070     cmsHTRANSFORM xform = nullptr;
0071 
0072     /* nothing set yet */
0073     if (cieXYZ == nullptr) {
0074         return ret;
0075     }
0076 
0077     /* convert the color to sRGB */
0078     profile_xyz = cmsCreateXYZProfile();
0079     profile_srgb = cmsCreate_sRGBProfile();
0080     xform = cmsCreateTransform(profile_xyz, TYPE_XYZ_DBL, profile_srgb, TYPE_RGB_8, INTENT_ABSOLUTE_COLORIMETRIC, 0);
0081     cmsDoTransform(xform, cieXYZ, &rgb, 1);
0082 
0083     ret.setRgb(rgb.R, rgb.G, rgb.B);
0084 
0085     if (profile_srgb != nullptr) {
0086         cmsCloseProfile(profile_srgb);
0087     }
0088     if (profile_xyz != nullptr) {
0089         cmsCloseProfile(profile_xyz);
0090     }
0091     if (xform != nullptr) {
0092         cmsDeleteTransform(xform);
0093     }
0094     return ret;
0095 }
0096 
0097 void Profile::parseProfile(const uint *data, size_t length)
0098 {
0099     /* save the length */
0100     m_size = length;
0101     m_loaded = true;
0102 
0103     /* ensure we have the header */
0104     if (length < 0x84) {
0105         qWarning() << "profile was not valid (file size too small)";
0106         m_errorMessage = i18n("file too small");
0107         m_loaded = false;
0108         return;
0109     }
0110 
0111     /* load profile into lcms */
0112     m_lcmsProfile = cmsOpenProfileFromMem(data, length);
0113     if (m_lcmsProfile == nullptr) {
0114         qWarning() << "failed to load: not an ICC profile";
0115         m_errorMessage = i18n("not an ICC profile");
0116         m_loaded = false;
0117         return;
0118     }
0119 
0120     /* get white point */
0121     cmsCIEXYZ *cie_xyz;
0122     bool ret;
0123     cie_xyz = static_cast<cmsCIEXYZ *>(cmsReadTag(m_lcmsProfile, cmsSigMediaWhitePointTag));
0124     if (cie_xyz != nullptr) {
0125         cmsCIExyY xyY;
0126         double temp_float;
0127         m_white.setX(cie_xyz->X);
0128         m_white.setY(cie_xyz->Y);
0129         m_white.setZ(cie_xyz->Z);
0130 
0131         /* convert to lcms xyY values */
0132         cmsXYZ2xyY(&xyY, cie_xyz);
0133         qDebug() << "whitepoint:" << xyY.x << xyY.y << xyY.Y;
0134 
0135         /* get temperature */
0136         ret = cmsTempFromWhitePoint(&temp_float, &xyY);
0137         if (ret) {
0138             /* round to nearest 100K */
0139             m_temperature = (((uint)temp_float) / 100) * 100;
0140             qDebug() << "color temperature:" << m_temperature;
0141         } else {
0142             m_temperature = 0;
0143             qWarning() << "failed to get color temperature";
0144         }
0145     } else {
0146         /* this is no big surprise, some profiles don't have these */
0147         m_white.setX(0);
0148         m_white.setY(0);
0149         m_white.setZ(0);
0150         qDebug() << "failed to get white point";
0151     }
0152 
0153     /* get the profile kind */
0154     cmsProfileClassSignature profile_class;
0155     profile_class = cmsGetDeviceClass(m_lcmsProfile);
0156     switch (profile_class) {
0157     case cmsSigInputClass:
0158         m_kind = KindInputDevice;
0159         break;
0160     case cmsSigDisplayClass:
0161         m_kind = KindDisplayDevice;
0162         break;
0163     case cmsSigOutputClass:
0164         m_kind = KindOutputDevice;
0165         break;
0166     case cmsSigLinkClass:
0167         m_kind = KindDeviceLink;
0168         break;
0169     case cmsSigColorSpaceClass:
0170         m_kind = KindColorspaceConversion;
0171         break;
0172     case cmsSigAbstractClass:
0173         m_kind = KindAbstract;
0174         break;
0175     case cmsSigNamedColorClass:
0176         m_kind = KindNamedColor;
0177         break;
0178     default:
0179         m_kind = KindUnknown;
0180     }
0181 
0182     /* get colorspace */
0183     cmsColorSpaceSignature color_space;
0184     color_space = cmsGetColorSpace(m_lcmsProfile);
0185     switch (color_space) {
0186     case cmsSigXYZData:
0187         m_colorspace = i18nc("colorspace", "XYZ");
0188         break;
0189     case cmsSigLabData:
0190         m_colorspace = i18nc("colorspace", "LAB");
0191         break;
0192     case cmsSigLuvData:
0193         m_colorspace = i18nc("colorspace", "LUV");
0194         break;
0195     case cmsSigYCbCrData:
0196         m_colorspace = i18nc("colorspace", "YCbCr");
0197         break;
0198     case cmsSigYxyData:
0199         m_colorspace = i18nc("colorspace", "Yxy");
0200         break;
0201     case cmsSigRgbData:
0202         m_colorspace = i18nc("colorspace", "RGB");
0203         break;
0204     case cmsSigGrayData:
0205         m_colorspace = i18nc("colorspace", "Gray");
0206         break;
0207     case cmsSigHsvData:
0208         m_colorspace = i18nc("colorspace", "HSV");
0209         break;
0210     case cmsSigCmykData:
0211         m_colorspace = i18nc("colorspace", "CMYK");
0212         break;
0213     case cmsSigCmyData:
0214         m_colorspace = i18nc("colorspace", "CMY");
0215         break;
0216     default:
0217         m_colorspace = i18nc("colorspace", "Unknown");
0218     }
0219 
0220     /* get the illuminants from the primaries */
0221     //    if (color_space == cmsSigRgbData) {
0222     //        cie_xyz = cmsReadTag (m_lcmsProfile, cmsSigRedMatrixColumnTag);
0223     //        if (cie_xyz != NULL) {
0224     //            /* assume that if red is present, the green and blue are too */
0225     //            cd_color_copy_xyz ((CdColorXYZ *) cie_xyz, (CdColorXYZ *) &cie_illum.Red);
0226     //            cie_xyz = cmsReadTag (m_lcmsProfile, cmsSigGreenMatrixColumnTag);
0227     //            cd_color_copy_xyz ((CdColorXYZ *) cie_xyz, (CdColorXYZ *) &cie_illum.Green);
0228     //            cie_xyz = cmsReadTag (m_lcmsProfile, cmsSigBlueMatrixColumnTag);
0229     //            cd_color_copy_xyz ((CdColorXYZ *) cie_xyz, (CdColorXYZ *) &cie_illum.Blue);
0230     //            got_illuminants = TRUE;
0231     //        } else {
0232     //            g_debug ("failed to get illuminants");
0233     //        }
0234     //    }
0235 
0236     /* get the illuminants by running it through the profile */
0237     //    if (!got_illuminants && color_space == cmsSigRgbData) {
0238     //        gdouble rgb_values[3];
0239 
0240     //        /* create a transform from profile to XYZ */
0241     //        xyz_profile = cmsCreateXYZProfile ();
0242     //        transform = cmsCreateTransform (m_lcmsProfile, TYPE_RGB_DBL, xyz_profile, TYPE_XYZ_DBL, INTENT_PERCEPTUAL, 0);
0243     //        if (transform != NULL) {
0244 
0245     //            /* red */
0246     //            rgb_values[0] = 1.0;
0247     //            rgb_values[1] = 0.0;
0248     //            rgb_values[2] = 0.0;
0249     //            cmsDoTransform (transform, rgb_values, &cie_illum.Red, 1);
0250 
0251     //            /* green */
0252     //            rgb_values[0] = 0.0;
0253     //            rgb_values[1] = 1.0;
0254     //            rgb_values[2] = 0.0;
0255     //            cmsDoTransform (transform, rgb_values, &cie_illum.Green, 1);
0256 
0257     //            /* blue */
0258     //            rgb_values[0] = 0.0;
0259     //            rgb_values[1] = 0.0;
0260     //            rgb_values[2] = 1.0;
0261     //            cmsDoTransform (transform, rgb_values, &cie_illum.Blue, 1);
0262 
0263     //            /* we're done */
0264     //            cmsDeleteTransform (transform);
0265     //            got_illuminants = TRUE;
0266     //        } else {
0267     //            g_debug ("failed to run through profile");
0268     //        }
0269 
0270     //        /* no more need for the output profile */
0271     //        cmsCloseProfile (xyz_profile);
0272     //    }
0273 
0274     /* we've got valid values */
0275     //    if (got_illuminants) {
0276     //        cd_color_set_xyz (priv->red,
0277     //                          cie_illum.Red.X, cie_illum.Red.Y, cie_illum.Red.Z);
0278     //        cd_color_set_xyz (priv->green,
0279     //                          cie_illum.Green.X, cie_illum.Green.Y, cie_illum.Green.Z);
0280     //        cd_color_set_xyz (priv->blue,
0281     //                          cie_illum.Blue.X, cie_illum.Blue.Y, cie_illum.Blue.Z);
0282     //    } else {
0283     //        g_debug ("failed to get luminance values");
0284     //        cd_color_clear_xyz (priv->red);
0285     //        cd_color_clear_xyz (priv->green);
0286     //        cd_color_clear_xyz (priv->blue);
0287     //    }
0288 
0289     /* get profile header version */
0290     cmsFloat64Number profile_version;
0291     profile_version = cmsGetProfileVersion(m_lcmsProfile);
0292     m_version = QString::number(profile_version, 'f', 2);
0293 
0294     /* allocate temporary buffer */
0295     wchar_t *text = new wchar_t[1024];
0296 
0297     /* get description */
0298     ret = cmsGetProfileInfo(m_lcmsProfile, cmsInfoDescription, "en", "US", text, 1024);
0299     if (ret) {
0300         m_description = QString::fromWCharArray(text).simplified();
0301         if (m_description.isEmpty()) {
0302             m_description = i18n("Missing description");
0303         }
0304     }
0305 
0306     /* get copyright */
0307     ret = cmsGetProfileInfo(m_lcmsProfile, cmsInfoCopyright, "en", "US", text, 1024);
0308     if (ret) {
0309         m_copyright = QString::fromWCharArray(text).simplified();
0310     }
0311 
0312     /* get description */
0313     ret = cmsGetProfileInfo(m_lcmsProfile, cmsInfoManufacturer, "en", "US", text, 1024);
0314     if (ret) {
0315         m_manufacturer = QString::fromWCharArray(text).simplified();
0316     }
0317 
0318     /* get description */
0319     ret = cmsGetProfileInfo(m_lcmsProfile, cmsInfoModel, "en", "US", text, 1024);
0320     if (ret) {
0321         m_model = QString::fromWCharArray(text).simplified();
0322     }
0323 
0324     delete[] text;
0325 
0326     //    /* success */
0327     //    ret = TRUE;
0328 
0329     QCryptographicHash hash(QCryptographicHash::Md5);
0330     hash.addData(reinterpret_cast<const char *>(data), length);
0331     m_checksum = hash.result().toHex();
0332     qDebug() << "checksum" << m_checksum;
0333 
0334     //    /* generate and set checksum */
0335     //    checksum = g_compute_checksum_for_data (G_CHECKSUM_MD5, (const guchar *) data, length);
0336     //    gcm_profile_set_checksum (profile, checksum);
0337     // out:
0338     //    g_free (text);
0339     //    g_free (checksum);
0340     //    return ret;
0341 }
0342 
0343 bool Profile::loaded() const
0344 {
0345     return m_loaded;
0346 }
0347 
0348 Profile::ProfileKind Profile::kind() const
0349 {
0350     return m_kind;
0351 }
0352 
0353 QString Profile::kindString() const
0354 {
0355     switch (kind()) {
0356     case KindInputDevice:
0357         return i18nc("profile kind", "Input device");
0358     case KindDisplayDevice:
0359         return i18nc("profile kind", "Display device");
0360     case KindOutputDevice:
0361         return i18nc("profile kind", "Output device");
0362     case KindDeviceLink:
0363         return i18nc("profile kind", "Devicelink");
0364     case KindColorspaceConversion:
0365         return i18nc("profile kind", "Colorspace conversion");
0366     case KindAbstract:
0367         return i18nc("profile kind", "Abstract");
0368     case KindNamedColor:
0369         return i18nc("profile kind", "Named color");
0370     default:
0371         return i18nc("profile kind", "Unknown");
0372     }
0373 }
0374 
0375 QString Profile::colorspace() const
0376 {
0377     return m_colorspace;
0378 }
0379 
0380 uint Profile::size() const
0381 {
0382     return m_size;
0383 }
0384 
0385 bool Profile::canDelete() const
0386 {
0387     return m_canDelete;
0388 }
0389 
0390 QString Profile::description() const
0391 {
0392     return m_description;
0393 }
0394 
0395 QString Profile::filename() const
0396 {
0397     return m_filename;
0398 }
0399 
0400 QString Profile::version() const
0401 {
0402     return m_version;
0403 }
0404 
0405 QString Profile::copyright() const
0406 {
0407     return m_copyright;
0408 }
0409 
0410 QString Profile::manufacturer() const
0411 {
0412     return m_manufacturer;
0413 }
0414 
0415 QString Profile::model() const
0416 {
0417     return m_model;
0418 }
0419 
0420 QString Profile::checksum() const
0421 {
0422     return m_checksum;
0423 }
0424 
0425 uint Profile::temperature() const
0426 {
0427     return m_temperature;
0428 }
0429 
0430 QMap<QString, QColor> Profile::getNamedColors()
0431 {
0432     QMap<QString, QColor> array;
0433     if (m_lcmsProfile == nullptr) {
0434         return array;
0435     }
0436 
0437     cmsCIELab lab;
0438     cmsCIEXYZ xyz;
0439     cmsHPROFILE profile_lab = nullptr;
0440     cmsHPROFILE profile_xyz = nullptr;
0441     cmsHTRANSFORM xform = nullptr;
0442     cmsNAMEDCOLORLIST *nc2 = nullptr;
0443     cmsUInt16Number pcs[3];
0444     cmsUInt32Number count;
0445     bool ret;
0446     char name[cmsMAX_PATH];
0447     char prefix[33];
0448     char suffix[33];
0449 
0450     /* setup a dummy transform so we can get all the named colors */
0451     profile_lab = cmsCreateLab2Profile(nullptr);
0452     profile_xyz = cmsCreateXYZProfile();
0453     xform = cmsCreateTransform(profile_lab, TYPE_Lab_DBL, profile_xyz, TYPE_XYZ_DBL, INTENT_ABSOLUTE_COLORIMETRIC, 0);
0454     if (xform == nullptr) {
0455         qWarning() << "no transform";
0456         goto out;
0457     }
0458 
0459     /* retrieve named color list from transform */
0460     nc2 = static_cast<cmsNAMEDCOLORLIST *>(cmsReadTag(m_lcmsProfile, cmsSigNamedColor2Tag));
0461     if (nc2 == nullptr) {
0462         qWarning() << "no named color list";
0463         goto out;
0464     }
0465 
0466     /* get the number of NCs */
0467     count = cmsNamedColorCount(nc2);
0468     if (count == 0) {
0469         qWarning() << "no named colors";
0470         goto out;
0471     }
0472 
0473     for (uint i = 0; i < count; ++i) {
0474         /* parse title */
0475         QString string;
0476         ret = cmsNamedColorInfo(nc2, i, name, prefix, suffix, (cmsUInt16Number *)&pcs, nullptr);
0477         if (!ret) {
0478             qWarning() << "failed to get NC #" << i;
0479             goto out;
0480         }
0481 
0482         //        if (prefix[0] != '\0') {
0483         //            string.append(prefix);
0484         // Add a space if we got the above prefix
0485         //        }
0486         string.append(name);
0487         if (suffix[0] != '\0') {
0488             string.append(suffix);
0489         }
0490 
0491         /* get color */
0492         cmsLabEncoded2Float((cmsCIELab *)&lab, pcs);
0493         cmsDoTransform(xform, &lab, &xyz, 1);
0494 
0495         QColor color;
0496         color = convertXYZ(&xyz);
0497         if (!color.isValid()) {
0498             continue;
0499         }
0500 
0501         // Store the color
0502         array[string] = color;
0503     }
0504 
0505 out:
0506     if (profile_lab != nullptr)
0507         cmsCloseProfile(profile_lab);
0508     if (profile_xyz != nullptr)
0509         cmsCloseProfile(profile_xyz);
0510     if (xform != nullptr)
0511         cmsDeleteTransform(xform);
0512     return array;
0513 }
0514 
0515 QString Profile::profileWithSource(const QString &dataSource, const QString &profilename, const QDateTime &created)
0516 {
0517     if (dataSource == QLatin1String(CD_PROFILE_METADATA_DATA_SOURCE_EDID)) {
0518         return i18n("Default: %1", profilename);
0519     } else if (dataSource == QLatin1String(CD_PROFILE_METADATA_DATA_SOURCE_STANDARD)) {
0520         return i18n("Colorspace: %1", profilename);
0521     } else if (dataSource == QLatin1String(CD_PROFILE_METADATA_DATA_SOURCE_TEST)) {
0522         return i18n("Test profile: %1", profilename);
0523     } else if (dataSource == QLatin1String(CD_PROFILE_METADATA_DATA_SOURCE_CALIB)) {
0524         return i18n("%1 (%2)", profilename, QLocale().toString(created, QLocale::LongFormat));
0525     }
0526     return profilename;
0527 }