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 }