/* * This file is part of the KDE project * * Copyright (c) 2005 Boudewijn * Copyright (c) 2007 Benjamin Schleimer * * 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 of the License, 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * * This implementation completely and utterly based on the gimp's bumpmap.c, * copyright: * Copyright (C) 1997 Federico Mena Quintero * Copyright (C) 1997-2000 Jens Lautenbacher * Copyright (C) 2000 Sven Neumann * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wdgbumpmap.h" #include "bumpmap.h" #define MOD(x, y) \ ((x) < 0 ? ((y) - 1 - ((y) - 1 - (x)) % (y)) : (x) % (y)) typedef KGenericFactory ChalkBumpmapFactory; K_EXPORT_COMPONENT_FACTORY( chalkbumpmap, ChalkBumpmapFactory( "chalk" ) ) ChalkBumpmap::ChalkBumpmap(TQObject *parent, const char *name, const TQStringList &) : KParts::Plugin(parent, name) { setInstance(ChalkBumpmapFactory::instance()); if (parent->inherits("KisFilterRegistry")) { KisFilterRegistry * manager = dynamic_cast(parent); manager->add(new KisFilterBumpmap()); } } ChalkBumpmap::~ChalkBumpmap() { } KisFilterBumpmap::KisFilterBumpmap() : KisFilter(id(), "map", i18n("&Bumpmap...")) { } namespace { void convertRow(KisPaintDevice * orig, TQ_UINT8 * row, TQ_INT32 x, TQ_INT32 y, TQ_INT32 w, TQ_UINT8 * lut, TQ_INT32 waterlevel) { KisColorSpace * csOrig = orig->colorSpace(); KisHLineIteratorPixel origIt = orig->createHLineIterator(x, y, w, false); for (int i = 0; i < w; ++i) { row[0] = csOrig->intensity8(origIt.rawData()); row[0] = lut[waterlevel + ((row[0] - waterlevel) * csOrig->getAlpha(origIt.rawData())) / 255]; ++row; ++origIt; } } } void KisFilterBumpmap::process(KisPaintDeviceSP src, KisPaintDeviceSP dst, KisFilterConfiguration* cfg, const TQRect& rect) { if (!src) return; if (!dst) return; if (!cfg) return; if (!rect.isValid()) return; if (rect.isNull()) return; if (rect.isEmpty()) return; KisBumpmapConfiguration * config = (KisBumpmapConfiguration*)cfg; TQ_INT32 xofs, yofs; /// The x,y offset values TQ_INT32 lx, ly; /* X and Y components of light vector */ TQ_INT32 nz2, nzlz; /* nz^2, nz*lz */ TQ_INT32 background; /* Shade for vertical normals */ double compensation; /* Background compensation */ TQ_UINT8 lut[256]; /* Look-up table for modes */ double azimuth; double elevation; TQ_INT32 lz, nz; TQ_INT32 i; double n; // ------------------ Prepare parameters /* Convert the offsets */ xofs = -config->xofs; yofs = -config->yofs; /* Convert to radians */ azimuth = M_PI * config->azimuth / 180.0; elevation = M_PI * config->elevation / 180.0; /* Calculate the light vector */ lx = (TQ_INT32)(cos(azimuth) * cos(elevation) * 255.0); ly = (TQ_INT32)(sin(azimuth) * cos(elevation) * 255.0); lz = (TQ_INT32)(sin(elevation) * 255.0); /* Calculate constant Z component of surface normal */ nz = (TQ_INT32)((6 * 255) / config->depth); nz2 = nz * nz; nzlz = nz * lz; /* Optimize for vertical normals */ background = lz; /* Calculate darkness compensation factor */ compensation = sin(elevation); /* Create look-up table for map type */ for (i = 0; i < 256; i++) { switch (config->type) { case SPHERICAL: n = i / 255.0 - 1.0; lut[i] = (int) (255.0 * sqrt(1.0 - n * n) + 0.5); break; case SINUSOIDAL: n = i / 255.0; lut[i] = (int) (255.0 * (sin((-M_PI / 2.0) + M_PI * n) + 1.0) / 2.0 + 0.5); break; case LINEAR: default: lut[i] = i; } if (config->invert) lut[i] = 255 - lut[i]; } // Crate a grayscale layer from the bumpmap layer. TQRect bmRect; KisPaintDevice * bumpmap; if (!config->bumpmap.isNull() && src->image()) { KisLayerSP l = src->image()->findLayer(config->bumpmap); KisPaintDeviceSP bumplayer = 0; KisPaintLayer * pl = dynamic_cast(l.data()); if (pl) { bumplayer = pl->paintDevice(); } else { KisGroupLayer * gl = dynamic_cast(l.data()); if (gl) { bumplayer = gl->projection(gl->extent()); } else { KisAdjustmentLayer * al = dynamic_cast(l.data()); if (al) { bumplayer = al->cachedPaintDevice(); } } } if (bumplayer) { bmRect = bumplayer->exactBounds(); bumpmap = bumplayer.data(); } else { bmRect = rect; bumpmap = src; } } if(!bmRect.isValid()) { bmRect = rect; bumpmap = src; } kdDebug(12345) << "KisFilterBumpmap::process: rect=" << rect << ", bumpmap rect=" << bmRect << "\n"; setProgressTotalSteps(rect.height()); // ---------------------- Load initial three bumpmap scanlines KisColorSpace * srcCs = src->colorSpace(); TQValueVector channels = srcCs->channels(); // One byte per pixel, converted from the bumpmap layer. TQ_UINT8 * bm_row1 = new TQ_UINT8[bmRect.width()]; TQ_UINT8 * bm_row2 = new TQ_UINT8[bmRect.width()]; TQ_UINT8 * bm_row3 = new TQ_UINT8[bmRect.width()]; TQ_UINT8 * tmp_row; // ------------------- Map the bumps TQ_INT32 yofs1, yofs2, yofs3; // ------------------- Initialize offsets if (config->tiled) { yofs2 = MOD (yofs, bmRect.height()); yofs1 = MOD (yofs2 - 1, bmRect.height()); yofs3 = MOD (yofs2 + 1, bmRect.height()); } else { yofs2 = 0; yofs1 = yofs2 - 1; yofs3 = yofs2 + 1; } convertRow(bumpmap, bm_row1, bmRect.x(), yofs1+bmRect.top(), bmRect.width(), lut, config->waterlevel); convertRow(bumpmap, bm_row2, bmRect.x(), yofs2+bmRect.top(), bmRect.width(), lut, config->waterlevel); convertRow(bumpmap, bm_row3, bmRect.x(), yofs3+bmRect.top(), bmRect.width(), lut, config->waterlevel); for (int y = rect.top(); y<=rect.bottom(); y++) { const TQ_INT32 yBump = y+yofs; if(config->tiled || (bmRect.top()<=yBump && yBump<=bmRect.bottom()) ) { // Get the iterators KisHLineIteratorPixel dstIt = dst->createHLineIterator(rect.x(), y, rect.width(), true); KisHLineIteratorPixel srcIt = src->createHLineIterator(rect.x(), y, rect.width(), false); //while (x < sel_w || cancelRequested()) { while (!srcIt.isDone() && !cancelRequested()) { if (srcIt.isSelected()) { const TQ_INT32 xBump = srcIt.x()+xofs; TQ_INT32 nx, ny; // Calculate surface normal from bumpmap if (config->tiled || bmRect.left() <= xBump && xBump <= bmRect.right()) { TQ_INT32 xofs1, xofs2, xofs3; if (config->tiled) { xofs2 = MOD (xBump-bmRect.left(), bmRect.width()); xofs1 = MOD (xofs2 - 1, bmRect.width()); xofs3 = MOD (xofs2 + 1, bmRect.width()); } else { xofs2 = MOD (xBump-bmRect.left(), bmRect.width()); xofs1 = ::max (xofs2 - 1, 0); xofs3 = ::min (xofs2 + 1, bmRect.width()); } nx = (bm_row1[xofs1] + bm_row2[xofs1] + bm_row3[xofs1] - bm_row1[xofs3] - bm_row2[xofs3] - bm_row3[xofs3]); ny = (bm_row3[xofs1] + bm_row3[xofs2] + bm_row3[xofs3] - bm_row1[xofs1] - bm_row1[xofs2] - bm_row1[xofs3]); } else { nx = 0; ny = 0; } // Shade TQ_INT32 shade; if ((nx == 0) && (ny == 0)) { shade = background; } else { TQ_INT32 ndotl = (nx * lx) + (ny * ly) + nzlz; if (ndotl < 0) { shade = (TQ_INT32)(compensation * config->ambient); } else { shade = (TQ_INT32)(ndotl / sqrt(nx * nx + ny * ny + nz2)); shade = (TQ_INT32)(shade + TQMAX(0, (255 * compensation - shade)) * config->ambient / 255); } } // Paint srcCs->darken(srcIt.rawData(), dstIt.rawData(), shade, config->compensate, compensation, 1); } ++srcIt; ++dstIt; } // Go to the next row tmp_row = bm_row1; bm_row1 = bm_row2; bm_row2 = bm_row3; bm_row3 = tmp_row; yofs2++; if (yofs2 >= bmRect.height()) { yofs2 = 0; } if (config->tiled) { yofs3 = MOD (yofs2 + 1, bmRect.height()); } else { yofs3 = yofs2 + 1; } convertRow(bumpmap, bm_row3, bmRect.x(), yofs3+bmRect.top(), bmRect.width(), lut, config->waterlevel); } incProgress(); } delete [] bm_row1; delete [] bm_row2; delete [] bm_row3; setProgressDone(); } KisFilterConfigWidget * KisFilterBumpmap::createConfigurationWidget(TQWidget* parent, KisPaintDeviceSP dev) { KisBumpmapConfigWidget * w = new KisBumpmapConfigWidget(this, dev, parent); return w; } KisFilterConfiguration * KisFilterBumpmap::configuration(TQWidget * w) { KisBumpmapConfigWidget * widget = dynamic_cast(w); if (widget == 0) { return new KisBumpmapConfiguration(); } else { return widget->config(); } } KisFilterConfiguration * KisFilterBumpmap::configuration() { return new KisBumpmapConfiguration(); } KisBumpmapConfiguration::KisBumpmapConfiguration() : KisFilterConfiguration( "bumpmap", 1 ) { bumpmap = TQString(); azimuth = 135.0; elevation = 45.0; depth = 3; xofs = 0; yofs = 0; waterlevel = 0; ambient = 0; compensate = true; invert = false; tiled = true; type = chalk::LINEAR; } void KisBumpmapConfiguration::fromXML(const TQString & s) { KisFilterConfiguration::fromXML( s ); bumpmap = TQString(); azimuth = 135.0; elevation = 45.0; depth = 3; xofs = 0; yofs = 0; waterlevel = 0; ambient = 0; compensate = true; invert = false; tiled = true; type = chalk::LINEAR; TQVariant v; v = getProperty("bumpmap"); if (v.isValid()) { bumpmap = v.asString(); } v = getProperty("azimuth"); if (v.isValid()) { azimuth = v.asDouble(); } v = getProperty("elevation"); if (v.isValid()) { elevation = v.asDouble();} v = getProperty("depth"); if (v.isValid()) { depth = v.asDouble(); } v = getProperty("xofs"); if (v.isValid()) { xofs = v.asInt(); } v = getProperty("yofs"); if (v.isValid()) { yofs = v.asInt();} v = getProperty("waterlevel"); if (v.isValid()) { waterlevel = v.asInt();} v = getProperty("ambient"); if (v.isValid()) { ambient = v.asInt();} v = getProperty("compensate"); if (v.isValid()) { compensate = v.asBool(); } v = getProperty("invert"); if (v.isValid()) { invert = v.asBool(); } v = getProperty("tiled"); if (v.isValid()) { tiled = v.asBool();} v = getProperty("type"); if (v.isValid()) { type = (enumBumpmapType)v.asInt(); } } TQString KisBumpmapConfiguration::toString() { m_properties.clear(); //setProperty("bumpmap", TQVariant(bumpmap)); setProperty("azimuth", TQVariant(azimuth)); setProperty("elevation", TQVariant(elevation)); setProperty("depth", TQVariant(depth)); setProperty("xofs", TQVariant(xofs)); setProperty("yofs", TQVariant(yofs)); setProperty("waterlevel", TQVariant(waterlevel)); setProperty("ambient", TQVariant(ambient)); setProperty("compensate", TQVariant(compensate)); setProperty("invert", TQVariant(invert)); setProperty("tiled", TQVariant(tiled)); setProperty("type", TQVariant(type)); return KisFilterConfiguration::toString(); } KisBumpmapConfigWidget::KisBumpmapConfigWidget(KisFilter *, KisPaintDeviceSP dev, TQWidget * parent, const char * name, WFlags f) : KisFilterConfigWidget(parent, name, f) { m_page = new WdgBumpmap(this); TQHBoxLayout * l = new TQHBoxLayout(this); TQ_CHECK_PTR(l); l->add(m_page); // Find all of the layers in the group if(dev->image() ) { KisGroupLayerSP root = dev->image()->rootLayer(); for(KisLayerSP layer = root->firstChild(); layer; layer = layer->nextSibling()) { m_page->cboxSourceLayer->insertItem(layer->name()); } } // Connect all of the widgets to update signal connect( m_page->radioLinear, TQT_SIGNAL( toggled(bool)), TQT_SIGNAL(sigPleaseUpdatePreview())); connect( m_page->radioSpherical, TQT_SIGNAL( toggled(bool)), TQT_SIGNAL(sigPleaseUpdatePreview())); connect( m_page->radioSinusoidal, TQT_SIGNAL( toggled(bool)), TQT_SIGNAL(sigPleaseUpdatePreview())); connect( m_page->chkCompensate, TQT_SIGNAL( toggled(bool)), TQT_SIGNAL(sigPleaseUpdatePreview())); connect( m_page->chkInvert, TQT_SIGNAL( toggled(bool)), TQT_SIGNAL(sigPleaseUpdatePreview())); connect( m_page->chkTiled, TQT_SIGNAL( toggled(bool)), TQT_SIGNAL(sigPleaseUpdatePreview())); connect( m_page->dblAzimuth, TQT_SIGNAL( valueChanged(int)), TQT_SIGNAL(sigPleaseUpdatePreview())); connect( m_page->dblElevation, TQT_SIGNAL( valueChanged(int)), TQT_SIGNAL(sigPleaseUpdatePreview())); connect( m_page->dblDepth, TQT_SIGNAL( valueChanged(int)), TQT_SIGNAL(sigPleaseUpdatePreview())); connect( m_page->intXOffset, TQT_SIGNAL( valueChanged(int)), TQT_SIGNAL(sigPleaseUpdatePreview())); connect( m_page->intYOffset, TQT_SIGNAL( valueChanged(int)), TQT_SIGNAL(sigPleaseUpdatePreview())); connect( m_page->intWaterLevel, TQT_SIGNAL( valueChanged(int)), TQT_SIGNAL(sigPleaseUpdatePreview())); connect( m_page->intAmbient, TQT_SIGNAL( valueChanged(int)), TQT_SIGNAL(sigPleaseUpdatePreview())); } KisBumpmapConfiguration * KisBumpmapConfigWidget::config() { KisBumpmapConfiguration * cfg = new KisBumpmapConfiguration(); cfg->bumpmap = m_page->cboxSourceLayer->currentText(); cfg->azimuth = m_page->dblAzimuth->value(); cfg->elevation = m_page->dblElevation->value(); cfg->depth = m_page->dblDepth->value(); cfg->xofs = m_page->intXOffset->value(); cfg->yofs = m_page->intYOffset->value(); cfg->waterlevel = m_page->intWaterLevel->value(); cfg->ambient = m_page->intAmbient->value(); cfg->compensate = m_page->chkCompensate->isChecked(); cfg->invert = m_page->chkInvert->isChecked(); cfg->tiled = m_page->chkTiled->isChecked(); cfg->type = (enumBumpmapType)m_page->grpType->selectedId(); return cfg; } void KisBumpmapConfigWidget::setConfiguration(KisFilterConfiguration * config) { KisBumpmapConfiguration * cfg = dynamic_cast(config); if (!cfg) return; // NOTE: maybe we should find the item instead? m_page->cboxSourceLayer->setCurrentText( cfg->bumpmap ); m_page->dblAzimuth->setValue(cfg->azimuth); m_page->dblElevation->setValue(cfg->elevation); m_page->dblDepth->setValue(cfg->depth); m_page->intXOffset->setValue(cfg->xofs); m_page->intYOffset->setValue(cfg->yofs); m_page->intWaterLevel->setValue(cfg->waterlevel); m_page->intAmbient->setValue(cfg->ambient); m_page->chkCompensate->setChecked(cfg->compensate); m_page->chkInvert->setChecked(cfg->invert); m_page->chkTiled->setChecked(cfg->tiled); m_page->grpType->setButton(cfg->type); } #include "bumpmap.moc"