/* vi: ts=8 sts=4 sw=4 * * This file is part of the KDE project, module kdecore. * Copyright (C) 2000 Geert Jansen * (C) 2007 Daniel M. Duley * with minor additions and based on ideas from * Torsten Rahn * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kiconeffect.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KIconEffectPrivate { public: int effect[6][3]; float value[6][3]; QColor color[6][3]; bool trans[6][3]; QString key[6][3]; QColor color2[6][3]; }; KIconEffect::KIconEffect() :d(new KIconEffectPrivate) { init(); } KIconEffect::~KIconEffect() { delete d; } void KIconEffect::init() { KSharedConfig::Ptr config = KGlobal::config(); int i, j, effect=-1; //FIXME: this really should be using KIconLoader::metaObject() to guarantee synchronization // performance wise it's also practically guaranteed to be faster QStringList groups; groups += "Desktop"; groups += "Toolbar"; groups += "MainToolbar"; groups += "Small"; groups += "Panel"; groups += "Dialog"; QStringList states; states += "Default"; states += "Active"; states += "Disabled"; QStringList::ConstIterator it, it2; QString _togray("togray"); QString _colorize("colorize"); QString _desaturate("desaturate"); QString _togamma("togamma"); QString _none("none"); QString _tomonochrome("tomonochrome"); for (it=groups.constBegin(), i=0; it!=groups.constEnd(); ++it, ++i) { // Default effects d->effect[i][0] = NoEffect; d->effect[i][1] = ((i==0)||(i==4)) ? ToGamma : NoEffect; d->effect[i][2] = ToGray; d->trans[i][0] = false; d->trans[i][1] = false; d->trans[i][2] = true; d->value[i][0] = 1.0; d->value[i][1] = ((i==0)||(i==4)) ? 0.7 : 1.0; d->value[i][2] = 1.0; d->color[i][0] = QColor(144,128,248); d->color[i][1] = QColor(169,156,255); d->color[i][2] = QColor(34,202,0); d->color2[i][0] = QColor(0,0,0); d->color2[i][1] = QColor(0,0,0); d->color2[i][2] = QColor(0,0,0); KConfigGroup cg(config, *it + "Icons"); for (it2=states.constBegin(), j=0; it2!=states.constEnd(); ++it2, ++j) { QString tmp = cg.readEntry(*it2 + "Effect", QString()); if (tmp == _togray) effect = ToGray; else if (tmp == _colorize) effect = Colorize; else if (tmp == _desaturate) effect = DeSaturate; else if (tmp == _togamma) effect = ToGamma; else if (tmp == _tomonochrome) effect = ToMonochrome; else if (tmp == _none) effect = NoEffect; else continue; if(effect != -1) d->effect[i][j] = effect; d->value[i][j] = cg.readEntry(*it2 + "Value", 0.0); d->color[i][j] = cg.readEntry(*it2 + "Color", QColor()); d->color2[i][j] = cg.readEntry(*it2 + "Color2", QColor()); d->trans[i][j] = cg.readEntry(*it2 + "SemiTransparent", false); } } } bool KIconEffect::hasEffect(int group, int state) const { if (group < 0 || group >= KIconLoader::LastGroup || state < 0 || state >= KIconLoader::LastState) { return false; } return d->effect[group][state] != NoEffect; } QString KIconEffect::fingerprint(int group, int state) const { if (group < 0 || group >= KIconLoader::LastGroup || state < 0 || state >= KIconLoader::LastState) { return QString(); } QString cached = d->key[group][state]; if (cached.isEmpty()) { QString tmp; cached = tmp.setNum(d->effect[group][state]); cached += ':'; cached += tmp.setNum(d->value[group][state]); cached += ':'; cached += d->trans[group][state] ? QLatin1String("trans") : QLatin1String("notrans"); if (d->effect[group][state] == Colorize || d->effect[group][state] == ToMonochrome) { cached += ':'; cached += d->color[group][state].name(); } if (d->effect[group][state] == ToMonochrome) { cached += ':'; cached += d->color2[group][state].name(); } d->key[group][state] = cached; } return cached; } QImage KIconEffect::apply(const QImage &image, int group, int state) const { if (state >= KIconLoader::LastState) { kDebug(265) << "Illegal icon state: " << state << "\n"; return image; } if (group >= KIconLoader::LastGroup) { kDebug(265) << "Illegal icon group: " << group << "\n"; return image; } return apply(image, d->effect[group][state], d->value[group][state], d->color[group][state], d->color2[group][state], d->trans[group][state]); } QImage KIconEffect::apply(const QImage &image, int effect, float value, const QColor &col, bool trans) const { return apply(image, effect, value, col, KColorScheme(QPalette::Active, KColorScheme::View).background().color(), trans); } QImage KIconEffect::apply(const QImage &img, int effect, float value, const QColor &col, const QColor &col2, bool trans) const { QImage image = img; if (effect >= LastEffect ) { kDebug(265) << "Illegal icon effect: " << effect << "\n"; return image; } if (value > 1.0) value = 1.0; else if (value < 0.0) value = 0.0; switch (effect) { case ToGray: toGray(image, value); break; case DeSaturate: deSaturate(image, value); break; case Colorize: colorize(image, col, value); break; case ToGamma: toGamma(image, value); break; case ToMonochrome: toMonochrome(image, col, col2, value); break; } if (trans == true) { semiTransparent(image); } return image; } QPixmap KIconEffect::apply(const QPixmap &pixmap, int group, int state) const { if (state >= KIconLoader::LastState) { kDebug(265) << "Illegal icon state: " << state << "\n"; return pixmap; } if (group >= KIconLoader::LastGroup) { kDebug(265) << "Illegal icon group: " << group << "\n"; return pixmap; } return apply(pixmap, d->effect[group][state], d->value[group][state], d->color[group][state], d->color2[group][state], d->trans[group][state]); } QPixmap KIconEffect::apply(const QPixmap &pixmap, int effect, float value, const QColor &col, bool trans) const { return apply(pixmap, effect, value, col, KColorScheme(QPalette::Active, KColorScheme::View).background().color(), trans); } QPixmap KIconEffect::apply(const QPixmap &pixmap, int effect, float value, const QColor &col, const QColor &col2, bool trans) const { QPixmap result; if (effect >= LastEffect ) { kDebug(265) << "Illegal icon effect: " << effect << "\n"; return result; } if ((trans == true) && (effect == NoEffect)) { result = pixmap; semiTransparent(result); } else if ( effect != NoEffect ) { QImage tmpImg = pixmap.toImage(); tmpImg = apply(tmpImg, effect, value, col, col2, trans); result = QPixmap::fromImage(tmpImg); } else result = pixmap; return result; } struct KIEImgEdit { QImage& img; QVector colors; unsigned int* data; unsigned int pixels; KIEImgEdit(QImage& _img):img(_img) { if (img.depth() > 8) { //Code using data and pixels assumes that the pixels are stored //in 32bit values and that the image is not premultiplied if ((img.format() != QImage::Format_ARGB32) && (img.format() != QImage::Format_RGB32)) { img = img.convertToFormat(QImage::Format_ARGB32); } data = (unsigned int*)img.bits(); pixels = img.width()*img.height(); } else { pixels = img.numColors(); colors = img.colorTable(); data = (unsigned int*)colors.data(); } } ~KIEImgEdit() { if (img.depth() <= 8) img.setColorTable(colors); } }; static bool painterSupportsAntialiasing() { QPaintEngine* const pe = QApplication::desktop()->paintEngine(); return pe && pe->hasFeature(QPaintEngine::Antialiasing); } // Taken from KImageEffect. We don't want to link kdecore to kdeui! As long // as this code is not too big, it doesn't seem much of a problem to me. void KIconEffect::toGray(QImage &img, float value) { if(value == 0.0) return; KIEImgEdit ii(img); QRgb *data = ii.data; QRgb *end = data + ii.pixels; unsigned char gray; if(value == 1.0){ while(data != end){ gray = qGray(*data); *data = qRgba(gray, gray, gray, qAlpha(*data)); ++data; } } else{ unsigned char val = (unsigned char)(255.0*value); while(data != end){ gray = qGray(*data); *data = qRgba((val*gray+(0xFF-val)*qRed(*data)) >> 8, (val*gray+(0xFF-val)*qGreen(*data)) >> 8, (val*gray+(0xFF-val)*qBlue(*data)) >> 8, qAlpha(*data)); ++data; } } } void KIconEffect::colorize(QImage &img, const QColor &col, float value) { if(value == 0.0) return; KIEImgEdit ii(img); QRgb *data = ii.data; QRgb *end = data + ii.pixels; float rcol = col.red(), gcol = col.green(), bcol = col.blue(); unsigned char red, green, blue, gray; unsigned char val = (unsigned char)(255.0*value); while(data != end){ gray = qGray(*data); if(gray < 128){ red = static_cast(rcol/128*gray); green = static_cast(gcol/128*gray); blue = static_cast(bcol/128*gray); } else if(gray > 128){ red = static_cast((gray-128)*(2-rcol/128)+rcol-1); green = static_cast((gray-128)*(2-gcol/128)+gcol-1); blue = static_cast((gray-128)*(2-bcol/128)+bcol-1); } else{ red = static_cast(rcol); green = static_cast(gcol); blue = static_cast(bcol); } *data = qRgba((val*red+(0xFF-val)*qRed(*data)) >> 8, (val*green+(0xFF-val)*qGreen(*data)) >> 8, (val*blue+(0xFF-val)*qBlue(*data)) >> 8, qAlpha(*data)); ++data; } } void KIconEffect::toMonochrome(QImage &img, const QColor &black, const QColor &white, float value) { if(value == 0.0) return; KIEImgEdit ii(img); QRgb *data = ii.data; QRgb *end = data + ii.pixels; // Step 1: determine the average brightness double values = 0.0, sum = 0.0; bool grayscale = true; while(data != end){ sum += qGray(*data)*qAlpha(*data) + 255*(255-qAlpha(*data)); values += 255; if((qRed(*data) != qGreen(*data) ) || (qGreen(*data) != qBlue(*data))) grayscale = false; ++data; } double medium = sum/values; // Step 2: Modify the image unsigned char val = (unsigned char)(255.0*value); int rw = white.red(), gw = white.green(), bw = white.blue(); int rb = black.red(), gb = black.green(), bb = black.blue(); data = ii.data; if(grayscale){ while(data != end){ if(qRed(*data) <= medium) *data = qRgba((val*rb+(0xFF-val)*qRed(*data)) >> 8, (val*gb+(0xFF-val)*qGreen(*data)) >> 8, (val*bb+(0xFF-val)*qBlue(*data)) >> 8, qAlpha(*data)); else *data = qRgba((val*rw+(0xFF-val)*qRed(*data)) >> 8, (val*gw+(0xFF-val)*qGreen(*data)) >> 8, (val*bw+(0xFF-val)*qBlue(*data)) >> 8, qAlpha(*data)); ++data; } } else{ while(data != end){ if(qGray(*data) <= medium) *data = qRgba((val*rb+(0xFF-val)*qRed(*data)) >> 8, (val*gb+(0xFF-val)*qGreen(*data)) >> 8, (val*bb+(0xFF-val)*qBlue(*data)) >> 8, qAlpha(*data)); else *data = qRgba((val*rw+(0xFF-val)*qRed(*data)) >> 8, (val*gw+(0xFF-val)*qGreen(*data)) >> 8, (val*bw+(0xFF-val)*qBlue(*data)) >> 8, qAlpha(*data)); ++data; } } } void KIconEffect::deSaturate(QImage &img, float value) { if(value == 0.0) return; KIEImgEdit ii(img); QRgb *data = ii.data; QRgb *end = data + ii.pixels; QColor color; int h, s, v; while(data != end){ color.setRgb(*data); color.getHsv(&h, &s, &v); color.setHsv(h, (int) (s * (1.0 - value) + 0.5), v); *data = qRgba(color.red(), color.green(), color.blue(), qAlpha(*data)); ++data; } } void KIconEffect::toGamma(QImage &img, float value) { KIEImgEdit ii(img); QRgb *data = ii.data; QRgb *end = data + ii.pixels; float gamma = 1/(2*value+0.5); while(data != end){ *data = qRgba(static_cast (pow(static_cast(qRed(*data))/255 , gamma)*255), static_cast (pow(static_cast(qGreen(*data))/255 , gamma)*255), static_cast (pow(static_cast(qBlue(*data))/255 , gamma)*255), qAlpha(*data)); ++data; } } void KIconEffect::semiTransparent(QImage &img) { int x, y; if(img.depth() == 32){ if(img.format() == QImage::Format_ARGB32_Premultiplied) img = img.convertToFormat(QImage::Format_ARGB32); int width = img.width(); int height = img.height(); if(painterSupportsAntialiasing()){ unsigned char *line; for(y=0; y>= 1; line += 4; } } } else{ for(y=0; y colorTable = img.colorTable(); for (int i = 0; i < colorTable.size(); ++i) { colorTable[i] = (colorTable[i] & 0x00ffffff) | ((colorTable[i] & 0xfe000000) >> 1); } img.setColorTable(colorTable); return; } } // Insert transparent pixel into the clut. int transColor = -1; // search for a color that is already transparent for(x=0; x= img.numColors()) return; img.setColor(transColor, 0); unsigned char *line; if(img.depth() == 8){ for(y=0; y> 3)) &= ~(1 << (x & 7)); else *(line + (x >> 3)) |= (1 << (x & 7)); } } } else{ for(y=0; y> 3)) &= ~(1 << (7-(x & 7))); else *(line + (x >> 3)) |= (1 << (7-(x & 7))); } } } } } } void KIconEffect::semiTransparent(QPixmap &pix) { if (painterSupportsAntialiasing()) { QImage img=pix.toImage(); semiTransparent(img); pix = QPixmap::fromImage(img); return; } QImage img; if (!pix.mask().isNull()) img = pix.mask().toImage(); else { img = QImage(pix.size(), QImage::Format_Mono); img.fill(1); } for (int y=0; y 255) { kDebug(265) << "Too many colors in src + overlay!\n"; return; } // Find transparent pixel in overlay int trans; for (trans=0; trans> 8; g2 = (a1 * g1 + (0xff - a1) * g2) >> 8; b2 = (a1 * b1 + (0xff - a1) * b2) >> 8; a2 = qMax(a1, a2); sline[j] = qRgba(r2, g2, b2, a2); } } } return; }