/* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2006-01-10 * Description : a widget to display CIE tongue from * an ICC profile. * * Copyright (C) 2006-2008 by Gilles Caulier * * Any source code are inspired from lprof project and * Copyright (C) 1998-2001 Marti Maria * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ // C++ includes. #include // TQt includes. #include #include #include #include #include // KDE includes. #include // Local includes. #include "ddebug.h" #include "lcmsprf.h" #include "cietonguewidget.h" #include "cietonguewidget.moc" namespace Digikam { // The following table gives the CIE colour matching functions // \bar{x}(\lambda), \bar{y}(\lambda), and \bar{z}(\lambda), for // wavelengths \lambda at 5 nanometre increments from 380 nm through // 780 nm. This table is used in conjunction with Planck's law for // the energy spectrum of a black body at a given temperature to plot // the black body curve on the CIE chart. // The following table gives the spectral chromaticity co-ordinates // x(\lambda) and y(\lambda) for wavelengths in 5 nanometre increments // from 380 nm through 780 nm. These co-ordinates represent the // position in the CIE x-y space of pure spectral colours of the given // wavelength, and thus define the outline of the CIE "tongue" // diagram. static const double spectral_chromaticity[81][3] = { { 0.1741, 0.0050 }, // 380 nm { 0.1740, 0.0050 }, { 0.1738, 0.0049 }, { 0.1736, 0.0049 }, { 0.1733, 0.0048 }, { 0.1730, 0.0048 }, { 0.1726, 0.0048 }, { 0.1721, 0.0048 }, { 0.1714, 0.0051 }, { 0.1703, 0.0058 }, { 0.1689, 0.0069 }, { 0.1669, 0.0086 }, { 0.1644, 0.0109 }, { 0.1611, 0.0138 }, { 0.1566, 0.0177 }, { 0.1510, 0.0227 }, { 0.1440, 0.0297 }, { 0.1355, 0.0399 }, { 0.1241, 0.0578 }, { 0.1096, 0.0868 }, { 0.0913, 0.1327 }, { 0.0687, 0.2007 }, { 0.0454, 0.2950 }, { 0.0235, 0.4127 }, { 0.0082, 0.5384 }, { 0.0039, 0.6548 }, { 0.0139, 0.7502 }, { 0.0389, 0.8120 }, { 0.0743, 0.8338 }, { 0.1142, 0.8262 }, { 0.1547, 0.8059 }, { 0.1929, 0.7816 }, { 0.2296, 0.7543 }, { 0.2658, 0.7243 }, { 0.3016, 0.6923 }, { 0.3373, 0.6589 }, { 0.3731, 0.6245 }, { 0.4087, 0.5896 }, { 0.4441, 0.5547 }, { 0.4788, 0.5202 }, { 0.5125, 0.4866 }, { 0.5448, 0.4544 }, { 0.5752, 0.4242 }, { 0.6029, 0.3965 }, { 0.6270, 0.3725 }, { 0.6482, 0.3514 }, { 0.6658, 0.3340 }, { 0.6801, 0.3197 }, { 0.6915, 0.3083 }, { 0.7006, 0.2993 }, { 0.7079, 0.2920 }, { 0.7140, 0.2859 }, { 0.7190, 0.2809 }, { 0.7230, 0.2770 }, { 0.7260, 0.2740 }, { 0.7283, 0.2717 }, { 0.7300, 0.2700 }, { 0.7311, 0.2689 }, { 0.7320, 0.2680 }, { 0.7327, 0.2673 }, { 0.7334, 0.2666 }, { 0.7340, 0.2660 }, { 0.7344, 0.2656 }, { 0.7346, 0.2654 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 } // 780 nm }; class CIETongueWidgetPriv { public: CIETongueWidgetPriv() { hMonitorProfile = 0; hXYZProfile = 0; hXFORM = 0; Measurement.Patches = 0; Measurement.Allowed = 0; blinkTimer = 0; pos = 0; profileDataAvailable = true; loadingImageMode = false; loadingImageSucess = false; } bool profileDataAvailable; bool loadingImageMode; bool loadingImageSucess; double gridside; int xBias; int yBias; int pxcols; int pxrows; int pos; // Position of animation during loading/calculation. TQPainter painter; TQPixmap pixmap; TQTimer *blinkTimer; cmsHPROFILE hMonitorProfile; cmsHPROFILE hXYZProfile; cmsHTRANSFORM hXFORM; cmsCIExyYTRIPLE Primaries; cmsCIEXYZ MediaWhite; MEASUREMENT Measurement; }; CIETongueWidget::CIETongueWidget(int w, int h, TQWidget *parent, cmsHPROFILE hMonitor) : TQWidget(parent, 0, TQt::WDestructiveClose) { d = new CIETongueWidgetPriv; d->blinkTimer = new TQTimer( this ); setMinimumSize(w, h); cmsErrorAction(LCMS_ERROR_SHOW); if (hMonitor) d->hMonitorProfile = hMonitor; else d->hMonitorProfile = cmsCreate_sRGBProfile(); d->hXYZProfile = cmsCreateXYZProfile(); d->hXFORM = cmsCreateTransform(d->hXYZProfile, TYPE_XYZ_16, d->hMonitorProfile, TYPE_RGB_8, INTENT_PERCEPTUAL, 0); connect(d->blinkTimer, TQ_SIGNAL(timeout()), this, TQ_SLOT(slotBlinkTimerDone())); } CIETongueWidget::~CIETongueWidget() { if (d->Measurement.Patches) free(d->Measurement.Patches); if (d->Measurement.Allowed) free(d->Measurement.Allowed); cmsDeleteTransform(d->hXFORM); cmsCloseProfile(d->hXYZProfile); cmsCloseProfile(d->hMonitorProfile); delete d; } int CIETongueWidget::grids(double val) const { return (int) floor(val * d->gridside + 0.5); } bool CIETongueWidget::setProfileData(const TQByteArray &profileData) { if (!profileData.isEmpty()) { cmsHPROFILE hProfile = cmsOpenProfileFromMem(const_cast(profileData.data()), (DWORD)profileData.size()); if (!hProfile) { d->profileDataAvailable = false; d->loadingImageSucess = false; } else { setProfile(hProfile); cmsCloseProfile(hProfile); d->profileDataAvailable = true; d->loadingImageSucess = true; } } else { d->profileDataAvailable = false; d->loadingImageSucess = false; } d->loadingImageMode = false; d->blinkTimer->stop(); repaint(false); return (d->profileDataAvailable); } bool CIETongueWidget::setProfileFromFile(const KURL& file) { if (!file.isEmpty() && file.isValid()) { cmsHPROFILE hProfile = cmsOpenProfileFromFile(TQFile::encodeName(file.path()), "r"); if (!hProfile) { d->profileDataAvailable = false; d->loadingImageSucess = false; } else { setProfile(hProfile); cmsCloseProfile(hProfile); d->profileDataAvailable = true; d->loadingImageSucess = true; } } else { d->profileDataAvailable = false; d->loadingImageSucess = false; } d->blinkTimer->stop(); repaint(false); return (d->profileDataAvailable); } void CIETongueWidget::setProfile(cmsHPROFILE hProfile) { // Get the white point. ZeroMemory(&(d->MediaWhite), sizeof(cmsCIEXYZ)); cmsTakeMediaWhitePoint(&(d->MediaWhite), hProfile); cmsCIExyY White; cmsXYZ2xyY(&White, &(d->MediaWhite)); // Get the colorant matrix. ZeroMemory(&(d->Primaries), sizeof(cmsCIExyYTRIPLE)); if (cmsIsTag(hProfile, icSigRedColorantTag) && cmsIsTag(hProfile, icSigGreenColorantTag) && cmsIsTag(hProfile, icSigBlueColorantTag)) { MAT3 Mat; if (cmsReadICCMatrixRGB2XYZ(&Mat, hProfile)) { // Undo chromatic adaptation if (cmsAdaptMatrixFromD50(&Mat, &White)) { cmsCIEXYZ tmp; tmp.X = Mat.v[0].n[0]; tmp.Y = Mat.v[1].n[0]; tmp.Z = Mat.v[2].n[0]; // ScaleToWhite(&MediaWhite, &tmp); cmsXYZ2xyY(&(d->Primaries.Red), &tmp); tmp.X = Mat.v[0].n[1]; tmp.Y = Mat.v[1].n[1]; tmp.Z = Mat.v[2].n[1]; // ScaleToWhite(&MediaWhite, &tmp); cmsXYZ2xyY(&(d->Primaries.Green), &tmp); tmp.X = Mat.v[0].n[2]; tmp.Y = Mat.v[1].n[2]; tmp.Z = Mat.v[2].n[2]; // ScaleToWhite(&MediaWhite, &tmp); cmsXYZ2xyY(&(d->Primaries.Blue), &tmp); } } } // Get target data stored in profile ZeroMemory(&(d->Measurement), sizeof(MEASUREMENT)); char* CharTarget; size_t CharTargetSize; if (cmsTakeCharTargetData(hProfile, &CharTarget, &CharTargetSize)) { LCMSHANDLE hSheet = cmsxIT8LoadFromMem(CharTarget, CharTargetSize); if (hSheet != NULL) { cmsxPCollLoadFromSheet(&(d->Measurement), hSheet); cmsxIT8Free(hSheet); cmsxPCollValidatePatches(&(d->Measurement), PATCH_HAS_XYZ|PATCH_HAS_RGB); } } } void CIETongueWidget::mapPoint(int& icx, int& icy, LPcmsCIExyY xyY) { icx = (int) floor((xyY->x * (d->pxcols - 1)) + .5); icy = (int) floor(((d->pxrows - 1) - xyY->y * (d->pxrows - 1)) + .5); } void CIETongueWidget::biasedLine(int x1, int y1, int x2, int y2) { d->painter.drawLine(x1 + d->xBias, y1, x2 + d->xBias, y2); } void CIETongueWidget::biasedText(int x, int y, TQString Txt) { d->painter.drawText(TQPoint(d->xBias + x, y), Txt); } TQRgb CIETongueWidget::colorByCoord(double x, double y) { // Get xyz components scaled from coordinates double cx = ((double) x) / (d->pxcols - 1); double cy = 1.0 - ((double) y) / (d->pxrows - 1); double cz = 1.0 - cx - cy; // Project xyz to XYZ space. Note that in this // particular case we are substituting XYZ with xyz cmsCIEXYZ XYZ = { cx , cy , cz }; WORD XYZW[3]; BYTE RGB[3]; cmsFloat2XYZEncoded(XYZW, &XYZ); cmsDoTransform(d->hXFORM, XYZW, RGB, 1); return tqRgb(RGB[0], RGB[1], RGB[2]); } void CIETongueWidget::outlineTongue() { int lx = 0, ly = 0; int fx=0, fy=0; for (int x = 380; x <= 700; x += 5) { int ix = (x - 380) / 5; cmsCIExyY p = {spectral_chromaticity[ix][0], spectral_chromaticity[ix][1], 1}; int icx, icy; mapPoint(icx, icy, &p); if (x > 380) { biasedLine(lx, ly, icx, icy); } else { fx = icx; fy = icy; } lx = icx; ly = icy; } biasedLine(lx, ly, fx, fy); } void CIETongueWidget::fillTongue() { TQImage Img = d->pixmap.convertToImage(); int x; for (int y = 0; y < d->pxrows; y++) { int xe = 0; // Find horizontal extents of tongue on this line. for (x = 0; x < d->pxcols; x++) { if ((TQColor) Img.pixel(x + d->xBias, y) != TQt::black) { for (xe = d->pxcols - 1; xe >= x; xe--) { if ((TQColor) Img.pixel(xe + d->xBias, y) != TQt::black) { break; } } break; } } if (x < d->pxcols) { for ( ; x <= xe; x++) { TQRgb Color = colorByCoord(x, y); Img.setPixel(x + d->xBias, y, Color); } } } d->pixmap.convertFromImage(Img, TQPixmap::AvoidDither ); } void CIETongueWidget::drawTongueAxis() { TQFont font; font.setPointSize(6); d->painter.setFont(font); d->painter.setPen(tqRgb(255, 255, 255)); biasedLine(0, 0, 0, d->pxrows - 1); biasedLine(0, d->pxrows-1, d->pxcols-1, d->pxrows - 1); for (int y = 1; y <= 9; y += 1) { TQString s; int xstart = (y * (d->pxcols - 1)) / 10; int ystart = (y * (d->pxrows - 1)) / 10; s.sprintf("0.%d", y); biasedLine(xstart, d->pxrows - grids(1), xstart, d->pxrows - grids(4)); biasedText(xstart - grids(11), d->pxrows + grids(15), s); s.sprintf("0.%d", 10 - y); biasedLine(0, ystart, grids(3), ystart); biasedText(grids(-25), ystart + grids(5), s); } } void CIETongueWidget::drawTongueGrid() { d->painter.setPen(tqRgb(80, 80, 80)); for (int y = 1; y <= 9; y += 1) { int xstart = (y * (d->pxcols - 1)) / 10; int ystart = (y * (d->pxrows - 1)) / 10; biasedLine(xstart, grids(4), xstart, d->pxrows - grids(4) - 1); biasedLine(grids(7), ystart, d->pxcols-1-grids(7), ystart); } } void CIETongueWidget::drawLabels() { TQFont font; font.setPointSize(5); d->painter.setFont(font); for (int x = 450; x <= 650; x += (x > 470 && x < 600) ? 5 : 10) { TQString wl; int bx = 0, by = 0, tx, ty; if (x < 520) { bx = grids(-22); by = grids(2); } else if (x < 535) { bx = grids(-8); by = grids(-6); } else { bx = grids(4); } int ix = (x - 380) / 5; cmsCIExyY p = {spectral_chromaticity[ix][0], spectral_chromaticity[ix][1], 1}; int icx, icy; mapPoint(icx, icy, &p); tx = icx + ((x < 520) ? grids(-2) : ((x >= 535) ? grids(2) : 0)); ty = icy + ((x < 520) ? 0 : ((x >= 535) ? grids(-1) : grids(-2))); d->painter.setPen(tqRgb(255, 255, 255)); biasedLine(icx, icy, tx, ty); TQRgb Color = colorByCoord(icx, icy); d->painter.setPen(Color); wl.sprintf("%d", x); biasedText(icx+bx, icy+by, wl); } } void CIETongueWidget::drawSmallElipse(LPcmsCIExyY xyY, BYTE r, BYTE g, BYTE b, int sz) { int icx, icy; mapPoint(icx, icy, xyY); d->painter.setPen(tqRgb(r, g, b)); d->painter.drawEllipse(icx + d->xBias- sz/2, icy-sz/2, sz, sz); } void CIETongueWidget::drawPatches() { for (int i=0; i < d->Measurement.nPatches; i++) { LPPATCH p = d->Measurement.Patches + i; if (d->Measurement.Allowed[i]) { LPcmsCIEXYZ XYZ = &p ->XYZ; cmsCIExyY xyY; cmsXYZ2xyY(&xyY, XYZ); drawSmallElipse(&xyY, 0, 0, 0, 4); if (p->dwFlags & PATCH_HAS_XYZ_PROOF) { if (p->XYZ.Y < 0.03) continue; if (p->XYZProof.Y < 0.03) continue; cmsCIExyY Pt; cmsXYZ2xyY(&Pt, &p->XYZProof); int icx1, icx2, icy1, icy2; mapPoint(icx1, icy1, &xyY); mapPoint(icx2, icy2, &Pt); if (icx2 < 5 || icy2 < 5 || icx1 < 5 || icy1 < 5) continue; d->painter.setPen(tqRgb(255, 255, 0)); biasedLine(icx1, icy1, icx2, icy2); } } } } void CIETongueWidget::drawColorantTriangle() { drawSmallElipse(&(d->Primaries.Red), 255, 128, 128, 6); drawSmallElipse(&(d->Primaries.Green), 128, 255, 128, 6); drawSmallElipse(&(d->Primaries.Blue), 128, 128, 255, 6); int x1, y1, x2, y2, x3, y3; mapPoint(x1, y1, &(d->Primaries.Red)); mapPoint(x2, y2, &(d->Primaries.Green)); mapPoint(x3, y3, &(d->Primaries.Blue)); d->painter.setPen(tqRgb(255, 255, 255)); biasedLine(x1, y1, x2, y2); biasedLine(x2, y2, x3, y3); biasedLine(x3, y3, x1, y1); } void CIETongueWidget::sweep_sRGB() { int r, g, b; cmsHPROFILE hXYZ, hsRGB; hXYZ = cmsCreateXYZProfile(); hsRGB = cmsCreate_sRGBProfile(); cmsHTRANSFORM xform = cmsCreateTransform(hsRGB, TYPE_RGB_16, hXYZ, TYPE_XYZ_16, INTENT_ABSOLUTE_COLORIMETRIC, cmsFLAGS_NOTPRECALC); WORD RGB[3], XYZ[3]; cmsCIEXYZ xyz, MediaWhite; cmsCIExyY xyY, WhitePt; int x1, y1; cmsTakeMediaWhitePoint(&MediaWhite, hsRGB); cmsXYZ2xyY(&WhitePt, &MediaWhite); for (r=0; r < 65536; r += 1024) { for (g=0; g < 65536; g += 1024) { for (b=0; b < 65536; b += 1024) { RGB[0] = r; RGB[1] = g; RGB[2] = b; cmsDoTransform(xform, RGB, XYZ, 1); cmsXYZEncoded2Float(&xyz, XYZ); cmsXYZ2xyY(&xyY, &xyz); mapPoint(x1, y1, &xyY); d->painter.drawPoint(x1 + d->xBias, y1); } } } cmsDeleteTransform(xform); cmsCloseProfile(hXYZ); cmsCloseProfile(hsRGB); } void CIETongueWidget::drawWhitePoint() { cmsCIExyY Whitem_pntxyY; cmsXYZ2xyY(&Whitem_pntxyY, &(d->MediaWhite)); drawSmallElipse(&Whitem_pntxyY, 255, 255, 255, 8); } void CIETongueWidget::loadingStarted() { d->pos = 0; d->loadingImageMode = true; d->loadingImageSucess = false; repaint(false); d->blinkTimer->start(200); } void CIETongueWidget::loadingFailed() { d->blinkTimer->stop(); d->pos = 0; d->loadingImageMode = false; d->loadingImageSucess = false; repaint(false); } void CIETongueWidget::paintEvent(TQPaintEvent*) { d->pixmap = TQPixmap(size()); d->pixmap.setOptimization(TQPixmap::BestOptim); // Widget is disable : drawing grayed frame. if ( !isEnabled() ) { d->painter.begin(&d->pixmap); d->painter.fillRect(0, 0, size().width(), size().height(), palette().disabled().background()); d->painter.setPen(TQPen(palette().disabled().foreground(), 1, TQt::SolidLine)); d->painter.drawRect(0, 0, width(), height()); d->painter.end(); bitBlt(this, 0, 0, &d->pixmap); return; } // Loading image mode. if (d->loadingImageMode && !d->loadingImageSucess) { // In first, we draw an animation. int asize = 24; TQPixmap anim(asize, asize); TQPainter p2; p2.begin(&anim, this); p2.fillRect(0, 0, asize, asize, palette().active().background()); p2.translate(asize/2, asize/2); d->pos = (d->pos + 10) % 360; p2.setPen(TQPen(palette().active().text())); p2.rotate(d->pos); for ( int i=0 ; i<12 ; i++ ) { p2.drawLine(asize/2-5, 0, asize/2-2, 0); p2.rotate(30); } p2.end(); // ... and we render busy text. d->painter.begin(&d->pixmap); d->painter.fillRect(0, 0, size().width(), size().height(), palette().active().background()); d->painter.drawPixmap(width()/2 - asize /2, asize, anim); d->painter.setPen(TQPen(palette().active().text(), 1, TQt::SolidLine)); d->painter.drawRect(0, 0, width(), height()); d->painter.drawText(0, 0, size().width(), size().height(), TQt::AlignCenter, i18n("Loading image...")); d->painter.end(); bitBlt(this, 0, 0, &d->pixmap); return; } // No profile data to show. if (!d->profileDataAvailable || (!d->loadingImageMode && !d->loadingImageSucess)) { d->painter.begin(&d->pixmap); d->painter.fillRect(0, 0, size().width(), size().height(), palette().active().background()); d->painter.setPen(TQPen(palette().active().text(), 1, TQt::SolidLine)); d->painter.drawRect(0, 0, width(), height()); d->painter.drawText(0, 0, size().width(), size().height(), TQt::AlignCenter, i18n("No profile available...")); d->painter.end(); bitBlt(this, 0, 0, &d->pixmap); return; } // Draw the CIE tongue curve. d->pixmap.fill(TQt::black); d->painter.begin(&d->pixmap); int pixcols = d->pixmap.width(); int pixrows = d->pixmap.height(); d->gridside = (TQMIN(pixcols, pixrows)) / 512.0; d->xBias = grids(32); d->yBias = grids(20); d->pxcols = pixcols - d->xBias; d->pxrows = pixrows - d->yBias; d->painter.setBackgroundColor(tqRgb(0, 0, 0)); d->painter.setPen(tqRgb(255, 255, 255)); outlineTongue(); fillTongue(); drawTongueAxis(); drawLabels(); drawTongueGrid(); if (d->MediaWhite.Y > 0.0) drawWhitePoint(); if (d->Primaries.Red.Y != 0.0) drawColorantTriangle(); if (d->Measurement.Patches && d->Measurement.Allowed) drawPatches(); d->painter.end(); bitBlt(this, 0, 0, &d->pixmap); } void CIETongueWidget::slotBlinkTimerDone() { repaint(false); d->blinkTimer->start( 200 ); } } // namespace Digikam