diff options
Diffstat (limited to 'kdefx')
-rw-r--r-- | kdefx/Mainpage.dox | 32 | ||||
-rw-r--r-- | kdefx/Makefile.am | 45 | ||||
-rw-r--r-- | kdefx/configure.in.in | 63 | ||||
-rw-r--r-- | kdefx/kcpuinfo.cpp | 176 | ||||
-rw-r--r-- | kdefx/kcpuinfo.h | 70 | ||||
-rw-r--r-- | kdefx/kdrawutil.cpp | 264 | ||||
-rw-r--r-- | kdefx/kdrawutil.h | 180 | ||||
-rw-r--r-- | kdefx/kimageeffect.cpp | 4927 | ||||
-rw-r--r-- | kdefx/kimageeffect.h | 807 | ||||
-rw-r--r-- | kdefx/kpixmap.cpp | 389 | ||||
-rw-r--r-- | kdefx/kpixmap.h | 213 | ||||
-rw-r--r-- | kdefx/kpixmapeffect.cpp | 325 | ||||
-rw-r--r-- | kdefx/kpixmapeffect.h | 218 | ||||
-rw-r--r-- | kdefx/kpixmapsplitter.cpp | 95 | ||||
-rw-r--r-- | kdefx/kpixmapsplitter.h | 123 | ||||
-rw-r--r-- | kdefx/kstyle.cpp | 2164 | ||||
-rw-r--r-- | kdefx/kstyle.h | 344 | ||||
-rw-r--r-- | kdefx/libkdefx.nmcheck | 12 | ||||
-rw-r--r-- | kdefx/libkdefx_weak.nmcheck | 3 |
19 files changed, 10450 insertions, 0 deletions
diff --git a/kdefx/Mainpage.dox b/kdefx/Mainpage.dox new file mode 100644 index 000000000..1cd8258fa --- /dev/null +++ b/kdefx/Mainpage.dox @@ -0,0 +1,32 @@ +/** + * \mainpage The KDEfx Library + * + * This library provides various classes related to image and pixmap + * manipulation, a class that provides information about CPU support + * for architecture specific features, as well as the base class for + * the %KDE widget styles. + * + * The two graphical effects classes, KImageEffect and KPixmapEffect, + * provide static methods for applying graphical effects to images and + * pixmaps respectively. KImageEffect also provides highly optimized + * methods for compositing images. + * + * A class that's related to those two is KPixmapSplitter, which is + * used for calculating the positions of items in pixmaps with + * multiple items arranged in rows and columns. + * Another is KPixmap, which extends QPixmap with the capability to + * ensure that a 256 color pixmap uses a specific system wide + * palette. + * + * The KCPUInfo class provides a means for applications to obtain + * information at runtime about processor support for certain + * architecture extensions that are useful when processing images, + * such as MMX, SSE, 3DNow! and AltiVec. + * + * KStyle is the base class for the %KDE widget styles. It simplifies + * and extends the QStyle API in order to make style coding easier. + * It also provides an internal menu transparency and drop shadow + * engine, which means that all styles inheriting this class will + * automatically support those features. + */ + diff --git a/kdefx/Makefile.am b/kdefx/Makefile.am new file mode 100644 index 000000000..d584ed2cb --- /dev/null +++ b/kdefx/Makefile.am @@ -0,0 +1,45 @@ + +# This file is part of the KDE libraries +# Copyright (C) 1997 Matthias Kalle Dalheimer (kalle@kde.org) +# (C) 1997 Stephan Kulow (coolo@kde.org) + +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. + +# 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 General Public License +# along with this library; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301, USA. + +#SUBDIRS = . tests + +INCLUDES= $(all_includes) + +lib_LTLIBRARIES = libkdefx.la +libkdefx_la_LDFLAGS = $(KDE_RPATH) $(KDE_MT_LDFLAGS) $(all_libraries) -no-undefined -version-info 6:0:2 +libkdefx_la_LIBADD = $(LIB_QT) $(LIB_XRENDER) +libkdefx_la_NMCHECK = $(srcdir)/libkdefx.nmcheck +libkdefx_la_NMCHECKWEAK = $(srcdir)/libkdefx_weak.nmcheck $(top_srcdir)/kdecore/libqt-mt_weak.nmcheck \ + $(top_srcdir)/kdecore/standard_weak.nmcheck + +include_HEADERS = kpixmap.h kpixmapsplitter.h \ + kpixmapeffect.h kimageeffect.h kdrawutil.h kstyle.h kcpuinfo.h + +libkdefx_la_SOURCES = kpixmap.cpp kpixmapsplitter.cpp \ + kpixmapeffect.cpp kimageeffect.cpp kdrawutil.cpp kstyle.cpp \ + kcpuinfo.cpp + +METASOURCES = AUTO + +EXTRA_DIST = Mainpage.dox + +DOXYGEN_REFERENCES = kdecore +include ../admin/Doxyfile.am + diff --git a/kdefx/configure.in.in b/kdefx/configure.in.in new file mode 100644 index 000000000..9afed6f96 --- /dev/null +++ b/kdefx/configure.in.in @@ -0,0 +1,63 @@ + +dnl ----------------------------------------------------- +dnl XRender check +dnl ----------------------------------------------------- +LIB_XRENDER= +if test "$kde_use_qt_emb" = "no" && test "$kde_use_qt_mac" = "no"; then + KDE_CHECK_HEADER(X11/extensions/Xrender.h, [xrender_h=yes], [xrender_h=no]) + if test "$xrender_h" = yes; then + KDE_CHECK_LIB(Xrender, XRenderComposite, [ + LIB_XRENDER=-lXrender + AC_DEFINE_UNQUOTED(HAVE_XRENDER, 1, [Defined if your system has XRender support]) + ], [], -lXext -lX11 $X_EXTRA_LIBS) + fi +fi +AC_SUBST(LIB_XRENDER) + +dnl ----------------------------------------------------- +dnl IA32 checks +dnl ----------------------------------------------------- +case $host_cpu in + i*86 ) + AC_MSG_CHECKING(for assembler support for IA32 extensions) + + dnl MMX check + AC_TRY_COMPILE(, [ __asm__("pxor %mm0, %mm0") ], + [ + echo $ECHO_N "MMX yes$ECHO_C" + AC_DEFINE_UNQUOTED(HAVE_X86_MMX, 1, [Define to 1 if the assembler supports MMX instructions.]) + ], [ echo $ECHO_N "MMX no$ECHO_C" ]) + + dnl SSE check + AC_TRY_COMPILE(,[ __asm__("xorps %xmm0, %xmm0") ], + [ + echo $ECHO_N ", SSE yes$ECHO_C" + AC_DEFINE_UNQUOTED(HAVE_X86_SSE, 1, [Define to 1 if the assembler supports SSE instructions.]) + ], [ echo $ECHO_N ", SSE no$ECHO_C" ]) + + dnl SSE2 check + AC_TRY_COMPILE(, [ __asm__("xorpd %xmm0, %xmm0") ], + [ + echo $ECHO_N ", SSE2 yes$ECHO_C" + AC_DEFINE_UNQUOTED(HAVE_X86_SSE2, 1, [Define to 1 if the assembler supports SSE2 instructions.]) + ], [ echo $ECHO_N ", SSE2 no$ECHO_C" ]) + + dnl 3DNOW check + AC_TRY_COMPILE(, [ __asm__("femms") ], + [ + echo $ECHO_N ", 3DNOW yes$ECHO_C" + AC_DEFINE_UNQUOTED(HAVE_X86_3DNOW, 1, [Define to 1 if the assembler supports 3DNOW instructions.]) + ], [ echo $ECHO_N ", 3DNOW no$ECHO_C" ]) + echo + ;; + powerpc ) + AC_MSG_CHECKING(for assembler support for AltiVec instructions) + dnl AltiVec check + AC_TRY_COMPILE(, [ __asm__("mtspr 256, %0\n\t" "vand %%v0, %%v0, %%v0" : : "r"(-1) ) ], + [ + echo $ECHO_N " yes$ECHO_C" + AC_DEFINE_UNQUOTED(HAVE_PPC_ALTIVEC, 1, [Define to 1 if the assembler supports AltiVec instructions.]) + ], [ echo $ECHO_N ", AltiVec no$ECHO_C" ]) + echo + ;; +esac diff --git a/kdefx/kcpuinfo.cpp b/kdefx/kcpuinfo.cpp new file mode 100644 index 000000000..97c99f8d9 --- /dev/null +++ b/kdefx/kcpuinfo.cpp @@ -0,0 +1,176 @@ +/* + * This file is part of the KDE libraries + * Copyright (C) 2003 Fredrik Höglund <fredrik@kde.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <csignal> +#include <csetjmp> + +#include <config.h> +#include "kcpuinfo.h" + + +#if defined(__GNUC__) || defined(__INTEL_COMPILER) +# define HAVE_GNU_INLINE_ASM +#endif + +typedef void (*kde_sighandler_t) (int); + +#ifdef __i386__ +static jmp_buf env; + +// Sighandler for the SSE OS support check +static void sighandler( int ) +{ + std::longjmp( env, 1 ); +} +#endif + +#ifdef __PPC__ +static sigjmp_buf KDE_NO_EXPORT jmpbuf; +static sig_atomic_t KDE_NO_EXPORT canjump = 0; + +static void KDE_NO_EXPORT sigill_handler( int sig ) +{ + if ( !canjump ) { + signal( sig, SIG_DFL ); + raise( sig ); + } + canjump = 0; + siglongjmp( jmpbuf, 1 ); +} +#endif + +static int getCpuFeatures() +{ + volatile int features = 0; + +#if defined( HAVE_GNU_INLINE_ASM ) +#if defined( __i386__ ) + bool haveCPUID = false; + bool have3DNOW = false; + int result = 0; + + // First check if the CPU supports the CPUID instruction + __asm__ __volatile__( + // Try to toggle the CPUID bit in the EFLAGS register + "pushf \n\t" // Push the EFLAGS register onto the stack + "popl %%ecx \n\t" // Pop the value into ECX + "movl %%ecx, %%edx \n\t" // Copy ECX to EDX + "xorl $0x00200000, %%ecx \n\t" // Toggle bit 21 (CPUID) in ECX + "pushl %%ecx \n\t" // Push the modified value onto the stack + "popf \n\t" // Pop it back into EFLAGS + + // Check if the CPUID bit was successfully toggled + "pushf \n\t" // Push EFLAGS back onto the stack + "popl %%ecx \n\t" // Pop the value into ECX + "xorl %%eax, %%eax \n\t" // Zero out the EAX register + "cmpl %%ecx, %%edx \n\t" // Compare ECX with EDX + "je .Lno_cpuid_support%= \n\t" // Jump if they're identical + "movl $1, %%eax \n\t" // Set EAX to true + ".Lno_cpuid_support%=: \n\t" + : "=a"(haveCPUID) : : "%ecx", "%edx" ); + + // If we don't have CPUID we won't have the other extensions either + if ( ! haveCPUID ) + return 0L; + + // Execute CPUID with the feature request bit set + __asm__ __volatile__( + "pushl %%ebx \n\t" // Save EBX + "movl $1, %%eax \n\t" // Set EAX to 1 (features request) + "cpuid \n\t" // Call CPUID + "popl %%ebx \n\t" // Restore EBX + : "=d"(result) : : "%eax", "%ecx" ); + + // Test bit 23 (MMX support) + if ( result & 0x00800000 ) + features |= KCPUInfo::IntelMMX; + + __asm__ __volatile__( + "pushl %%ebx \n\t" + "movl $0x80000000, %%eax \n\t" + "cpuid \n\t" + "cmpl $0x80000000, %%eax \n\t" + "jbe .Lno_extended%= \n\t" + "movl $0x80000001, %%eax \n\t" + "cpuid \n\t" + "test $0x80000000, %%edx \n\t" + "jz .Lno_extended%= \n\t" + "movl $1, %%eax \n\t" // // Set EAX to true + ".Lno_extended%=: \n\t" + "popl %%ebx \n\t" // Restore EBX + : "=a"(have3DNOW) : ); + + if ( have3DNOW ) + features |= KCPUInfo::AMD3DNOW; + +#ifdef HAVE_X86_SSE + // Test bit 25 (SSE support) + if ( result & 0x00200000 ) { + features |= KCPUInfo::IntelSSE; + + // OS support test for SSE. + // Install our own sighandler for SIGILL. + kde_sighandler_t oldhandler = std::signal( SIGILL, sighandler ); + + // Try executing an SSE insn to see if we get a SIGILL + if ( setjmp( env ) ) + features ^= KCPUInfo::IntelSSE; // The OS support test failed + else + __asm__ __volatile__("xorps %xmm0, %xmm0"); + + // Restore the default sighandler + std::signal( SIGILL, oldhandler ); + + // Test bit 26 (SSE2 support) + if ( (result & 0x00400000) && (features & KCPUInfo::IntelSSE) ) + features |= KCPUInfo::IntelSSE2; + + // Note: The OS requirements for SSE2 are the same as for SSE + // so we don't have to do any additional tests for that. + } +#endif // HAVE_X86_SSE +#elif defined __PPC__ && defined HAVE_PPC_ALTIVEC + signal( SIGILL, sigill_handler ); + if ( sigsetjmp( jmpbuf, 1 ) ) { + signal( SIGILL, SIG_DFL ); + } else { + canjump = 1; + __asm__ __volatile__( "mtspr 256, %0\n\t" + "vand %%v0, %%v0, %%v0" + : /* none */ + : "r" (-1) ); + signal( SIGILL, SIG_DFL ); + features |= KCPUInfo::AltiVec; + } +#endif // __i386__ +#endif //HAVE_GNU_INLINE_ASM + + return features; +} + +unsigned int KCPUInfo::s_features = getCpuFeatures(); + + diff --git a/kdefx/kcpuinfo.h b/kdefx/kcpuinfo.h new file mode 100644 index 000000000..ce39ded82 --- /dev/null +++ b/kdefx/kcpuinfo.h @@ -0,0 +1,70 @@ +/* + * This file is part of the KDE libraries + * Copyright (C) 2003 Fredrik Höglund <fredrik@kde.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __KCPUINFO_H +#define __KCPUINFO_H + +#include <kdelibs_export.h> + +/** + * This class provides a means for applications to obtain information at + * runtime about processor support for certain architecture extensions, + * such as MMX, SSE, 3DNow and AltiVec. + * + * @since 3.2 + */ +class KDEFX_EXPORT KCPUInfo +{ + public: + /** + * This enum contains the list of architecture extensions you + * can query. + */ + enum Extensions { + IntelMMX = 1 << 0, //!< Intel's MMX instructions. + IntelSSE = 1 << 1, //!< Intel's SSE instructions. + IntelSSE2 = 1 << 2, //!< Intel's SSE2 instructions. + AMD3DNOW = 1 << 3, //!< AMD 3DNOW instructions + AltiVec = 1 << 4 //!< Motorola AltiVec instructions + }; + + /** + * Returns true if the processor supports @p extension, + * and false otherwise. + * + * @param extension the feature to query. + * @return If true, the processor supports @p extension. + * @see Extensions + */ + static bool haveExtension( unsigned int extension ) + { return (s_features & extension) != 0; } + + private: + static unsigned int s_features; +}; + +#endif + diff --git a/kdefx/kdrawutil.cpp b/kdefx/kdrawutil.cpp new file mode 100644 index 000000000..7ba703886 --- /dev/null +++ b/kdefx/kdrawutil.cpp @@ -0,0 +1,264 @@ +/* This file is part of the KDE libraries + Copyright (C) 1999 Daniel M. Duley <mosfet@kde.org> + + 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 "kdrawutil.h" +#include <qdrawutil.h> + +KDEFX_EXPORT void kDrawNextButton(QPainter *p, int x, int y, int w, int h, + const QColorGroup &g, bool sunken, + const QBrush *fill) +{ + QPen oldPen = p->pen(); + int x2 = x+w-1; + int y2 = y+h-1; + p->fillRect(x+1, y+1, w-2, h-2, + fill ? *fill : g.brush(QColorGroup::Button)); + p->setPen(sunken ? Qt::black : g.light()); + p->drawLine(x, y, x2-1, y); + p->drawLine(x, y, x, y2-1); + p->setPen(sunken ? g.midlight() : g.mid()); + p->drawLine(x+1, y2-1, x2-1, y2-1); + p->drawLine(x2-1, y+1, x2-1, y2-1); + p->setPen(sunken ? g.light() : Qt::black); + p->drawLine(x, y2, x2, y2); + p->drawLine(x2, y, x2, y2); + p->setPen(oldPen); +} + + +KDEFX_EXPORT void kDrawNextButton(QPainter *p, const QRect &r, const QColorGroup &g, + bool sunken, const QBrush *fill) +{ + kDrawNextButton(p, r.x(), r.y(), r.width(), r.height(), g, sunken, fill); +} + +KDEFX_EXPORT void kDrawBeButton(QPainter *p, int x, int y, int w, int h, + const QColorGroup &g, bool sunken, const QBrush *fill) +{ + QPen oldPen = p->pen(); + int x2 = x+w-1; + int y2 = y+h-1; + p->setPen(g.dark()); + p->drawLine(x+1, y, x2-1, y); + p->drawLine(x, y+1, x, y2-1); + p->drawLine(x+1, y2, x2-1, y2); + p->drawLine(x2, y+1, x2, y2-1); + + + if(!sunken){ + p->setPen(g.light()); + p->drawLine(x+2, y+2, x2-1, y+2); + p->drawLine(x+2, y+3, x2-2, y+3); + p->drawLine(x+2, y+4, x+2, y2-1); + p->drawLine(x+3, y+4, x+3, y2-2); + } + else{ + p->setPen(g.mid()); + p->drawLine(x+2, y+2, x2-1, y+2); + p->drawLine(x+2, y+3, x2-2, y+3); + p->drawLine(x+2, y+4, x+2, y2-1); + p->drawLine(x+3, y+4, x+3, y2-2); + } + + + p->setPen(sunken? g.light() : g.mid()); + p->drawLine(x2-1, y+2, x2-1, y2-1); + p->drawLine(x+2, y2-1, x2-1, y2-1); + + p->setPen(g.mid()); + p->drawLine(x+1, y+1, x2-1, y+1); + p->drawLine(x+1, y+2, x+1, y2-1); + p->drawLine(x2-2, y+3, x2-2, y2-2); + + if(fill) + p->fillRect(x+4, y+4, w-6, h-6, *fill); + + p->setPen(oldPen); +} + +KDEFX_EXPORT void kDrawBeButton(QPainter *p, QRect &r, const QColorGroup &g, bool sunken, + const QBrush *fill) +{ + kDrawBeButton(p, r.x(), r.y(), r.width(), r.height(), g, sunken, fill); +} + +KDEFX_EXPORT void kDrawRoundButton(QPainter *p, const QRect &r, const QColorGroup &g, + bool sunken) +{ + int x, y, x2, y2; + r.coords(&x, &y, &x2, &y2); + if(r.width() > 16 && r.height() > 16){ + QPen oldPen = p->pen(); + QPointArray hPntArray, lPntArray; + hPntArray.putPoints(0, 12, x+4,y+1, x+5,y+1, // top left + x+3,y+2, x+2,y+3, x+1,y+4, x+1,y+5, + x+1,y2-5, x+1,y2-4, x+2,y2-3, // half corners + x2-5,y+1, x2-4,y+1, x2-3,y+2); + + lPntArray.putPoints(0, 17, x2-5,y2-1, x2-4,y2-1, // btm right + x2-3,y2-2, x2-2,y2-3, x2-1,y2-5, x2-1,y2-4, + + x+3,y2-2, x+4,y2-1, x+5,y2-1, //half corners + x2-2,y+3, x2-1,y+4, x2-1,y+5, + + x2-5,y2-2, x2-4,y2-2, // testing + x2-3,y2-3, + x2-2,y2-5, x2-2,y2-4); + + p->setPen(sunken ? g.dark() : g.light()); + p->drawLine(x+6, y, x2-6, y); + p->drawLine(0, y+6, 0, y2-6); + p->drawPoints(hPntArray); + + p->setPen(sunken ? g.light() : g.dark()); + p->drawLine(x+6, y2, x2-6, y2); + p->drawLine(x+6, y2-1, x2-6, y2-1); + p->drawLine(x2, y+6, x2, y2-6); + p->drawLine(x2-1, y+6, x2-1, y2-6); + p->drawPoints(lPntArray); + p->setPen(oldPen); + } + else + qDrawWinPanel(p, x, y, r.width(), r.height(), g, sunken); +} + +KDEFX_EXPORT void kDrawRoundButton(QPainter *p, int x, int y, int w, int h, + const QColorGroup &g, bool sunken) +{ + QRect r(x, y, w, h); + kDrawRoundButton(p, r, g, sunken); +} + +#define QCOORDARRLEN(x) sizeof(x)/(sizeof(QCOORD)*2) + +KDEFX_EXPORT void kDrawRoundMask(QPainter *p, int x, int y, int w, int h, bool clear) +{ + // round edge fills + static const QCOORD btm_left_fill[]={ 0,0,1,0,2,0,3,0,4,0,0,1,1,1,2,1,3,1,4,1, + 1,2,2,2,3,2,4,2,2,3,3,3,4,3,3,4,4,4 }; + + static const QCOORD btm_right_fill[]={ 0,0,1,0,2,0,3,0,4,0,0,1,1,1,2,1,3,1,4, + 1,0,2,1,2,2,2,3,2,0,3,1,3,2,3,0,4,1,4 }; + + static const QCOORD top_left_fill[]={ 3,0,4,0,2,1,3,1,4,1,1,2,2,2,3,2,4,2,0,3, + 1,3,2,3,3,3,4,3,0,4,1,4,2,4,3,4,4,4 }; + + static const QCOORD top_right_fill[]={ 0,0,1,0,0,1,1,1,2,1,0,2,1,2,2,2,3,2,0, + 3,1,3,2,3,3,3,4,3,0,4,1,4,2,4,3,4,4,4 }; + + if(clear) + p->fillRect(x, y, w, h, QBrush(Qt::color0, Qt::SolidPattern)); + + QBrush fillBrush(Qt::color1, Qt::SolidPattern); + p->setPen(Qt::color1); + if(w > 16 && h > 16){ + int x2 = x+w-1; + int y2 = y+h-1; + QPointArray a(QCOORDARRLEN(top_left_fill), top_left_fill); + a.translate(1, 1); + p->drawPoints(a); + a.setPoints(QCOORDARRLEN(btm_left_fill), btm_left_fill); + a.translate(1, h-6); + p->drawPoints(a); + a.setPoints(QCOORDARRLEN(top_right_fill), top_right_fill); + a.translate(w-6, 1); + p->drawPoints(a); + a.setPoints(QCOORDARRLEN(btm_right_fill), btm_right_fill); + a.translate(w-6, h-6); + p->drawPoints(a); + + p->fillRect(x+6, y, w-12, h, fillBrush); + p->fillRect(x, y+6, x+6, h-12, fillBrush); + p->fillRect(x2-6, y+6, x2, h-12, fillBrush); + p->drawLine(x+6, y, x2-6, y); + p->drawLine(x+6, y2, x2-6, y2); + p->drawLine(x, y+6, x, y2-6); + p->drawLine(x2, y+6, x2, y2-6); + + } + else + p->fillRect(x, y, w, h, fillBrush); +} + +KDEFX_EXPORT void kRoundMaskRegion(QRegion &r, int x, int y, int w, int h) +{ + // using a bunch of QRect lines seems much more efficient than bitmaps or + // point arrays, even tho it uses more statements + r += QRect(x+6, y+0, w-12, h); + r += QRect(x+5, y+1, 1, h-2); // left + r += QRect(x+4, y+1, 1, h-2); + r += QRect(x+3, y+2, 1, h-4); + r += QRect(x+2, y+3, 1, h-6); + r += QRect(x+1, y+4, 1, h-8); + r += QRect(x, y+6, 1, h-12); + int x2 = x+w-1; + r += QRect(x2-5, y+1, 1, h-2); // right + r += QRect(x2-4, y+1, 1, h-2); + r += QRect(x2-3, y+2, 1, h-4); + r += QRect(x2-2, y+3, 1, h-6); + r += QRect(x2-1, y+4, 1, h-8); + r += QRect(x2, y+6, 1, h-12); +} + +KDEFX_EXPORT void kColorBitmaps(QPainter *p, const QColorGroup &g, int x, int y, + QBitmap *lightColor, QBitmap *midColor, + QBitmap *midlightColor, QBitmap *darkColor, + QBitmap *blackColor, QBitmap *whiteColor) +{ + QBitmap *bitmaps[]={lightColor, midColor, midlightColor, darkColor, + blackColor, whiteColor}; + + QColor colors[]={g.light(), g.mid(), g.midlight(), g.dark(), + Qt::black, Qt::white}; + + int i; + for(i=0; i < 6; ++i){ + if(bitmaps[i]){ + if(!bitmaps[i]->mask()) + bitmaps[i]->setMask(*bitmaps[i]); + p->setPen(colors[i]); + p->drawPixmap(x, y, *bitmaps[i]); + } + } +} + +KDEFX_EXPORT void kColorBitmaps(QPainter *p, const QColorGroup &g, int x, int y, int w, + int h, bool isXBitmaps, const uchar *lightColor, + const uchar *midColor, const uchar *midlightColor, + const uchar *darkColor, const uchar *blackColor, + const uchar *whiteColor) +{ + const uchar *data[]={lightColor, midColor, midlightColor, darkColor, + blackColor, whiteColor}; + + QColor colors[]={g.light(), g.mid(), g.midlight(), g.dark(), + Qt::black, Qt::white}; + + int i; + QBitmap b; + for(i=0; i < 6; ++i){ + if(data[i]){ + b = QBitmap(w, h, data[i], isXBitmaps); + b.setMask(b); + p->setPen(colors[i]); + p->drawPixmap(x, y, b); + } + } +} + + + diff --git a/kdefx/kdrawutil.h b/kdefx/kdrawutil.h new file mode 100644 index 000000000..f16a7ab02 --- /dev/null +++ b/kdefx/kdrawutil.h @@ -0,0 +1,180 @@ +/* This file is part of the KDE libraries + Copyright (C) 1999 Daniel M. Duley <mosfet@kde.org> + + 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. +*/ +#ifndef __KDRAWUTIL_H +#define __KDRAWUTIL_H + +#include <qnamespace.h> +#include <qpainter.h> +#include <qbitmap.h> +#include <qpalette.h> + +#include <kdelibs_export.h> + +/* + * Various drawing routines. Also see Qt's qdrawutil.h for some more routines + * contained in Qt. + * + * (C) Daniel M. Duley <mosfet@kde.org> + */ + +/** + * @relates KStyle + * @c \#include @c <kdrawutil.h> + * + * Draws a Next-style button (solid black shadow with light and midlight highlight). + * + * @param p The painter to use for drawing the button. + * @param r Specifies the rect in which to draw the button. + * @param g Specifies the shading colors. + * @param sunken Whether to draw the button as sunken (pressed) or not. + * @param fill The brush to use for filling the interior of the button. + * Pass @a null to prevent the button from being filled. + */ +KDEFX_EXPORT void kDrawNextButton(QPainter *p, const QRect &r, const QColorGroup &g, + bool sunken=false, const QBrush *fill=0); + +/** + * @relates KStyle + * @overload + */ +KDEFX_EXPORT void kDrawNextButton(QPainter *p, int x, int y, int w, int h, + const QColorGroup &g, bool sunken=false, + const QBrush *fill=0); + +/** + * @relates KStyle + * @c \#include @c <kdrawutil.h> + * + * Draws a Be-style button. + * + * @param p The painter to use for drawing the button. + * @param r Specifies the rect in which to draw the button. + * @param g Specifies the shading colors. + * @param sunken Whether to draw the button as sunken (pressed) or not. + * @param fill The brush to use for filling the interior of the button. + * Pass @a null to prevent the button from being filled. + */ +KDEFX_EXPORT void kDrawBeButton(QPainter *p, QRect &r, const QColorGroup &g, + bool sunken=false, const QBrush *fill=0); + +/** + * @relates KStyle + * @c \#include @c <kdrawutil.h> + * @overload + */ +KDEFX_EXPORT void kDrawBeButton(QPainter *p, int x, int y, int w, int h, + const QColorGroup &g, bool sunken=false, + const QBrush *fill=0); + +/** + * @relates KStyle + * @c \#include @c <kdrawutil.h> + * + * Draws a rounded oval button. This function doesn't fill the button. + * See kRoundMaskRegion() for setting masks for fills. + * + * @param p The painter to use for drawing the button. + * @param r Specifies the rect in which to draw the button. + * @param g Specifies the shading colors. + * @param sunken Whether to draw the button as sunken (pressed) or not. + */ +KDEFX_EXPORT void kDrawRoundButton(QPainter *p, const QRect &r, const QColorGroup &g, + bool sunken=false); + +/** + * @relates KStyle + * @overload + */ +KDEFX_EXPORT void kDrawRoundButton(QPainter *p, int x, int y, int w, int h, + const QColorGroup &g, bool sunken=false); + +/** + * @relates KStyle + * @c \#include @c <kdrawutil.h> + * + * Sets a region to the pixels covered by a round button of the given + * size. You can use this to set clipping regions. + * + * @param r Reference to the region to set. + * @param x The X coordinate of the button. + * @param y The Y coordinate of the button. + * @param w The width of the button. + * @param h The height of the button. + * + * @see kDrawRoundButton() and kDrawRoundMask() + */ +KDEFX_EXPORT void kRoundMaskRegion(QRegion &r, int x, int y, int w, int h); + +/** + * @relates KStyle + * @c \#include @c <kdrawutil.h> + * + * Paints the pixels covered by a round button of the given size with + * Qt::color1. This function is useful in QStyle::drawControlMask(). + * + * @param p The painter to use for drawing the button. + * @param x The X coordinate of the button. + * @param y The Y coordinate of the button. + * @param w The width of the button. + * @param h The height of the button. + * @param clear Whether to clear the rectangle specified by @p (x, y, w, h) to + * Qt::color0 before drawing the mask. + */ +KDEFX_EXPORT void kDrawRoundMask(QPainter *p, int x, int y, int w, int h, bool clear=false); + +/** + * @relates KStyle + * @c \#include @c <kdrawutil.h> + * + * Paints the provided bitmaps in the painter, using the supplied colorgroup for + * the foreground colors. There's one bitmap for each color. If you want to skip + * a color, pass @a null for the corresponding bitmap. + * + * @note The bitmaps will be self-masked automatically if not masked + * prior to calling this routine. + * + * @param p The painter to use for drawing the bitmaps. + * @param g Specifies the shading colors. + * @param x The X coordinate at which to draw the bitmaps. + * @param y The Y coordinate at which to draw the bitmaps. + * @param lightColor The bitmap to use for the light part. + * @param midColor The bitmap to use for the mid part. + * @param midlightColor The bitmap to use for the midlight part. + * @param darkColor The bitmap to use for the dark part. + * @param blackColor The bitmap to use for the black part. + * @param whiteColor The bitmap to use for the white part. + * + * @see QColorGroup + */ +KDEFX_EXPORT void kColorBitmaps(QPainter *p, const QColorGroup &g, int x, int y, + QBitmap *lightColor=0, QBitmap *midColor=0, + QBitmap *midlightColor=0, QBitmap *darkColor=0, + QBitmap *blackColor=0, QBitmap *whiteColor=0); + +/** + * @relates KStyle + * @c \#include @c <kdrawutil.h> + * @overload + */ + KDEFX_EXPORT void kColorBitmaps(QPainter *p, const QColorGroup &g, int x, int y, int w, + int h, bool isXBitmaps=true, const uchar *lightColor = 0, + const uchar *midColor=0, const uchar *midlightColor=0, + const uchar *darkColor=0, const uchar *blackColor=0, + const uchar *whiteColor=0); + +#endif diff --git a/kdefx/kimageeffect.cpp b/kdefx/kimageeffect.cpp new file mode 100644 index 000000000..b2a859563 --- /dev/null +++ b/kdefx/kimageeffect.cpp @@ -0,0 +1,4927 @@ +/* This file is part of the KDE libraries + Copyright (C) 1998, 1999, 2001, 2002 Daniel M. Duley <mosfet@kde.org> + (C) 1998, 1999 Christian Tibirna <ctibirna@total.net> + (C) 1998, 1999 Dirk Mueller <mueller@kde.org> + (C) 1999 Geert Jansen <g.t.jansen@stud.tue.nl> + (C) 2000 Josef Weidendorfer <weidendo@in.tum.de> + (C) 2004 Zack Rusin <zack@kde.org> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +// $Id$ + +#include <math.h> +#include <assert.h> + +#include <qimage.h> +#include <stdlib.h> +#include <iostream> + +#include "kimageeffect.h" +#include "kcpuinfo.h" + +#include <config.h> + +#if 0 +//disabled until #74478 fixed. + +#if defined(__i386__) && ( defined(__GNUC__) || defined(__INTEL_COMPILER) ) +# if defined( HAVE_X86_MMX ) +# define USE_MMX_INLINE_ASM +# endif +# if defined( HAVE_X86_SSE2 ) +# define USE_SSE2_INLINE_ASM +# endif +#endif + +#endif +//====================================================================== +// +// Utility stuff for effects ported from ImageMagick to QImage +// +//====================================================================== +#define MaxRGB 255L +#define DegreesToRadians(x) ((x)*M_PI/180.0) +#define MagickSQ2PI 2.50662827463100024161235523934010416269302368164062 +#define MagickEpsilon 1.0e-12 +#define MagickPI 3.14159265358979323846264338327950288419716939937510 +#define MOD(x, y) ((x) < 0 ? ((y) - 1 - ((y) - 1 - (x)) % (y)) : (x) % (y)) + +/** + * \relates KGlobal + * A typesafe function that returns x if it's between low and high values. + * low if x is smaller than then low and high if x is bigger than high. + */ +#define FXCLAMP(x,low,high) fxClamp(x,low,high) +template<class T> +inline const T& fxClamp( const T& x, const T& low, const T& high ) +{ + if ( x < low ) return low; + else if ( x > high ) return high; + else return x; +} + +static inline unsigned int intensityValue(unsigned int color) +{ + return((unsigned int)((0.299*qRed(color) + + 0.587*qGreen(color) + + 0.1140000000000001*qBlue(color)))); +} + +template<typename T> +static inline void liberateMemory(T **memory) +{ + assert(memory != NULL); + if(*memory == NULL) return; + free((char*)*memory); + *memory=NULL; +} + +struct double_packet +{ + double red; + double green; + double blue; + double alpha; +}; + +struct short_packet +{ + unsigned short int red; + unsigned short int green; + unsigned short int blue; + unsigned short int alpha; +}; + + +//====================================================================== +// +// Gradient effects +// +//====================================================================== + +QImage KImageEffect::gradient(const QSize &size, const QColor &ca, + const QColor &cb, GradientType eff, int ncols) +{ + int rDiff, gDiff, bDiff; + int rca, gca, bca, rcb, gcb, bcb; + + QImage image(size, 32); + + if (size.width() == 0 || size.height() == 0) { +#ifndef NDEBUG + std::cerr << "WARNING: KImageEffect::gradient: invalid image" << std::endl; +#endif + return image; + } + + register int x, y; + + rDiff = (rcb = cb.red()) - (rca = ca.red()); + gDiff = (gcb = cb.green()) - (gca = ca.green()); + bDiff = (bcb = cb.blue()) - (bca = ca.blue()); + + if( eff == VerticalGradient || eff == HorizontalGradient ){ + + uint *p; + uint rgb; + + register int rl = rca << 16; + register int gl = gca << 16; + register int bl = bca << 16; + + if( eff == VerticalGradient ) { + + int rcdelta = ((1<<16) / size.height()) * rDiff; + int gcdelta = ((1<<16) / size.height()) * gDiff; + int bcdelta = ((1<<16) / size.height()) * bDiff; + + for ( y = 0; y < size.height(); y++ ) { + p = (uint *) image.scanLine(y); + + rl += rcdelta; + gl += gcdelta; + bl += bcdelta; + + rgb = qRgb( (rl>>16), (gl>>16), (bl>>16) ); + + for( x = 0; x < size.width(); x++ ) { + *p = rgb; + p++; + } + } + + } + else { // must be HorizontalGradient + + unsigned int *o_src = (unsigned int *)image.scanLine(0); + unsigned int *src = o_src; + + int rcdelta = ((1<<16) / size.width()) * rDiff; + int gcdelta = ((1<<16) / size.width()) * gDiff; + int bcdelta = ((1<<16) / size.width()) * bDiff; + + for( x = 0; x < size.width(); x++) { + + rl += rcdelta; + gl += gcdelta; + bl += bcdelta; + + *src++ = qRgb( (rl>>16), (gl>>16), (bl>>16)); + } + + src = o_src; + + // Believe it or not, manually copying in a for loop is faster + // than calling memcpy for each scanline (on the order of ms...). + // I think this is due to the function call overhead (mosfet). + + for (y = 1; y < size.height(); ++y) { + + p = (unsigned int *)image.scanLine(y); + src = o_src; + for(x=0; x < size.width(); ++x) + *p++ = *src++; + } + } + } + + else { + + float rfd, gfd, bfd; + float rd = rca, gd = gca, bd = bca; + + unsigned char *xtable[3]; + unsigned char *ytable[3]; + + unsigned int w = size.width(), h = size.height(); + xtable[0] = new unsigned char[w]; + xtable[1] = new unsigned char[w]; + xtable[2] = new unsigned char[w]; + ytable[0] = new unsigned char[h]; + ytable[1] = new unsigned char[h]; + ytable[2] = new unsigned char[h]; + w*=2, h*=2; + + if ( eff == DiagonalGradient || eff == CrossDiagonalGradient) { + // Diagonal dgradient code inspired by BlackBox (mosfet) + // BlackBox dgradient is (C) Brad Hughes, <bhughes@tcac.net> and + // Mike Cole <mike@mydot.com>. + + rfd = (float)rDiff/w; + gfd = (float)gDiff/w; + bfd = (float)bDiff/w; + + int dir; + for (x = 0; x < size.width(); x++, rd+=rfd, gd+=gfd, bd+=bfd) { + dir = eff == DiagonalGradient? x : size.width() - x - 1; + xtable[0][dir] = (unsigned char) rd; + xtable[1][dir] = (unsigned char) gd; + xtable[2][dir] = (unsigned char) bd; + } + rfd = (float)rDiff/h; + gfd = (float)gDiff/h; + bfd = (float)bDiff/h; + rd = gd = bd = 0; + for (y = 0; y < size.height(); y++, rd+=rfd, gd+=gfd, bd+=bfd) { + ytable[0][y] = (unsigned char) rd; + ytable[1][y] = (unsigned char) gd; + ytable[2][y] = (unsigned char) bd; + } + + for (y = 0; y < size.height(); y++) { + unsigned int *scanline = (unsigned int *)image.scanLine(y); + for (x = 0; x < size.width(); x++) { + scanline[x] = qRgb(xtable[0][x] + ytable[0][y], + xtable[1][x] + ytable[1][y], + xtable[2][x] + ytable[2][y]); + } + } + } + + else if (eff == RectangleGradient || + eff == PyramidGradient || + eff == PipeCrossGradient || + eff == EllipticGradient) + { + int rSign = rDiff>0? 1: -1; + int gSign = gDiff>0? 1: -1; + int bSign = bDiff>0? 1: -1; + + rfd = (float)rDiff / size.width(); + gfd = (float)gDiff / size.width(); + bfd = (float)bDiff / size.width(); + + rd = (float)rDiff/2; + gd = (float)gDiff/2; + bd = (float)bDiff/2; + + for (x = 0; x < size.width(); x++, rd-=rfd, gd-=gfd, bd-=bfd) + { + xtable[0][x] = (unsigned char) abs((int)rd); + xtable[1][x] = (unsigned char) abs((int)gd); + xtable[2][x] = (unsigned char) abs((int)bd); + } + + rfd = (float)rDiff/size.height(); + gfd = (float)gDiff/size.height(); + bfd = (float)bDiff/size.height(); + + rd = (float)rDiff/2; + gd = (float)gDiff/2; + bd = (float)bDiff/2; + + for (y = 0; y < size.height(); y++, rd-=rfd, gd-=gfd, bd-=bfd) + { + ytable[0][y] = (unsigned char) abs((int)rd); + ytable[1][y] = (unsigned char) abs((int)gd); + ytable[2][y] = (unsigned char) abs((int)bd); + } + + int h = (size.height()+1)>>1; + for (y = 0; y < h; y++) { + unsigned int *sl1 = (unsigned int *)image.scanLine(y); + unsigned int *sl2 = (unsigned int *)image.scanLine(QMAX(size.height()-y-1, y)); + + int w = (size.width()+1)>>1; + int x2 = size.width()-1; + + for (x = 0; x < w; x++, x2--) { + unsigned int rgb = 0; + if (eff == PyramidGradient) { + rgb = qRgb(rcb-rSign*(xtable[0][x]+ytable[0][y]), + gcb-gSign*(xtable[1][x]+ytable[1][y]), + bcb-bSign*(xtable[2][x]+ytable[2][y])); + } + if (eff == RectangleGradient) { + rgb = qRgb(rcb - rSign * + QMAX(xtable[0][x], ytable[0][y]) * 2, + gcb - gSign * + QMAX(xtable[1][x], ytable[1][y]) * 2, + bcb - bSign * + QMAX(xtable[2][x], ytable[2][y]) * 2); + } + if (eff == PipeCrossGradient) { + rgb = qRgb(rcb - rSign * + QMIN(xtable[0][x], ytable[0][y]) * 2, + gcb - gSign * + QMIN(xtable[1][x], ytable[1][y]) * 2, + bcb - bSign * + QMIN(xtable[2][x], ytable[2][y]) * 2); + } + if (eff == EllipticGradient) { + rgb = qRgb(rcb - rSign * + (int)sqrt((xtable[0][x]*xtable[0][x] + + ytable[0][y]*ytable[0][y])*2.0), + gcb - gSign * + (int)sqrt((xtable[1][x]*xtable[1][x] + + ytable[1][y]*ytable[1][y])*2.0), + bcb - bSign * + (int)sqrt((xtable[2][x]*xtable[2][x] + + ytable[2][y]*ytable[2][y])*2.0)); + } + + sl1[x] = sl2[x] = rgb; + sl1[x2] = sl2[x2] = rgb; + } + } + } + + delete [] xtable[0]; + delete [] xtable[1]; + delete [] xtable[2]; + delete [] ytable[0]; + delete [] ytable[1]; + delete [] ytable[2]; + } + + // dither if necessary + if (ncols && (QPixmap::defaultDepth() < 15 )) { + if ( ncols < 2 || ncols > 256 ) + ncols = 3; + QColor *dPal = new QColor[ncols]; + for (int i=0; i<ncols; i++) { + dPal[i].setRgb ( rca + rDiff * i / ( ncols - 1 ), + gca + gDiff * i / ( ncols - 1 ), + bca + bDiff * i / ( ncols - 1 ) ); + } + dither(image, dPal, ncols); + delete [] dPal; + } + + return image; +} + + +// ----------------------------------------------------------------------------- + +//CT this was (before Dirk A. Mueller's speedup changes) +// merely the same code as in the above method, but it's supposedly +// way less performant since it introduces a lot of supplementary tests +// and simple math operations for the calculus of the balance. +// (surprizingly, it isn't less performant, in the contrary :-) +// Yes, I could have merged them, but then the excellent performance of +// the balanced code would suffer with no other gain than a mere +// source code and byte code size economy. + +QImage KImageEffect::unbalancedGradient(const QSize &size, const QColor &ca, + const QColor &cb, GradientType eff, int xfactor, int yfactor, + int ncols) +{ + int dir; // general parameter used for direction switches + + bool _xanti = false , _yanti = false; + + if (xfactor < 0) _xanti = true; // negative on X direction + if (yfactor < 0) _yanti = true; // negative on Y direction + + xfactor = abs(xfactor); + yfactor = abs(yfactor); + + if (!xfactor) xfactor = 1; + if (!yfactor) yfactor = 1; + + if (xfactor > 200 ) xfactor = 200; + if (yfactor > 200 ) yfactor = 200; + + + // float xbal = xfactor/5000.; + // float ybal = yfactor/5000.; + float xbal = xfactor/30./size.width(); + float ybal = yfactor/30./size.height(); + float rat; + + int rDiff, gDiff, bDiff; + int rca, gca, bca, rcb, gcb, bcb; + + QImage image(size, 32); + + if (size.width() == 0 || size.height() == 0) { +#ifndef NDEBUG + std::cerr << "WARNING: KImageEffect::unbalancedGradient : invalid image\n"; +#endif + return image; + } + + register int x, y; + unsigned int *scanline; + + rDiff = (rcb = cb.red()) - (rca = ca.red()); + gDiff = (gcb = cb.green()) - (gca = ca.green()); + bDiff = (bcb = cb.blue()) - (bca = ca.blue()); + + if( eff == VerticalGradient || eff == HorizontalGradient){ + QColor cRow; + + uint *p; + uint rgbRow; + + if( eff == VerticalGradient) { + for ( y = 0; y < size.height(); y++ ) { + dir = _yanti ? y : size.height() - 1 - y; + p = (uint *) image.scanLine(dir); + rat = 1 - exp( - (float)y * ybal ); + + cRow.setRgb( rcb - (int) ( rDiff * rat ), + gcb - (int) ( gDiff * rat ), + bcb - (int) ( bDiff * rat ) ); + + rgbRow = cRow.rgb(); + + for( x = 0; x < size.width(); x++ ) { + *p = rgbRow; + p++; + } + } + } + else { + + unsigned int *src = (unsigned int *)image.scanLine(0); + for(x = 0; x < size.width(); x++ ) + { + dir = _xanti ? x : size.width() - 1 - x; + rat = 1 - exp( - (float)x * xbal ); + + src[dir] = qRgb(rcb - (int) ( rDiff * rat ), + gcb - (int) ( gDiff * rat ), + bcb - (int) ( bDiff * rat )); + } + + // Believe it or not, manually copying in a for loop is faster + // than calling memcpy for each scanline (on the order of ms...). + // I think this is due to the function call overhead (mosfet). + + for(y = 1; y < size.height(); ++y) + { + scanline = (unsigned int *)image.scanLine(y); + for(x=0; x < size.width(); ++x) + scanline[x] = src[x]; + } + } + } + + else { + int w=size.width(), h=size.height(); + + unsigned char *xtable[3]; + unsigned char *ytable[3]; + xtable[0] = new unsigned char[w]; + xtable[1] = new unsigned char[w]; + xtable[2] = new unsigned char[w]; + ytable[0] = new unsigned char[h]; + ytable[1] = new unsigned char[h]; + ytable[2] = new unsigned char[h]; + + if ( eff == DiagonalGradient || eff == CrossDiagonalGradient) + { + for (x = 0; x < w; x++) { + dir = _xanti ? x : w - 1 - x; + rat = 1 - exp( - (float)x * xbal ); + + xtable[0][dir] = (unsigned char) ( rDiff/2 * rat ); + xtable[1][dir] = (unsigned char) ( gDiff/2 * rat ); + xtable[2][dir] = (unsigned char) ( bDiff/2 * rat ); + } + + for (y = 0; y < h; y++) { + dir = _yanti ? y : h - 1 - y; + rat = 1 - exp( - (float)y * ybal ); + + ytable[0][dir] = (unsigned char) ( rDiff/2 * rat ); + ytable[1][dir] = (unsigned char) ( gDiff/2 * rat ); + ytable[2][dir] = (unsigned char) ( bDiff/2 * rat ); + } + + for (y = 0; y < h; y++) { + unsigned int *scanline = (unsigned int *)image.scanLine(y); + for (x = 0; x < w; x++) { + scanline[x] = qRgb(rcb - (xtable[0][x] + ytable[0][y]), + gcb - (xtable[1][x] + ytable[1][y]), + bcb - (xtable[2][x] + ytable[2][y])); + } + } + } + + else if (eff == RectangleGradient || + eff == PyramidGradient || + eff == PipeCrossGradient || + eff == EllipticGradient) + { + int rSign = rDiff>0? 1: -1; + int gSign = gDiff>0? 1: -1; + int bSign = bDiff>0? 1: -1; + + for (x = 0; x < w; x++) + { + dir = _xanti ? x : w - 1 - x; + rat = 1 - exp( - (float)x * xbal ); + + xtable[0][dir] = (unsigned char) abs((int)(rDiff*(0.5-rat))); + xtable[1][dir] = (unsigned char) abs((int)(gDiff*(0.5-rat))); + xtable[2][dir] = (unsigned char) abs((int)(bDiff*(0.5-rat))); + } + + for (y = 0; y < h; y++) + { + dir = _yanti ? y : h - 1 - y; + + rat = 1 - exp( - (float)y * ybal ); + + ytable[0][dir] = (unsigned char) abs((int)(rDiff*(0.5-rat))); + ytable[1][dir] = (unsigned char) abs((int)(gDiff*(0.5-rat))); + ytable[2][dir] = (unsigned char) abs((int)(bDiff*(0.5-rat))); + } + + for (y = 0; y < h; y++) { + unsigned int *scanline = (unsigned int *)image.scanLine(y); + for (x = 0; x < w; x++) { + if (eff == PyramidGradient) + { + scanline[x] = qRgb(rcb-rSign*(xtable[0][x]+ytable[0][y]), + gcb-gSign*(xtable[1][x]+ytable[1][y]), + bcb-bSign*(xtable[2][x]+ytable[2][y])); + } + else if (eff == RectangleGradient) + { + scanline[x] = qRgb(rcb - rSign * + QMAX(xtable[0][x], ytable[0][y]) * 2, + gcb - gSign * + QMAX(xtable[1][x], ytable[1][y]) * 2, + bcb - bSign * + QMAX(xtable[2][x], ytable[2][y]) * 2); + } + else if (eff == PipeCrossGradient) + { + scanline[x] = qRgb(rcb - rSign * + QMIN(xtable[0][x], ytable[0][y]) * 2, + gcb - gSign * + QMIN(xtable[1][x], ytable[1][y]) * 2, + bcb - bSign * + QMIN(xtable[2][x], ytable[2][y]) * 2); + } + else if (eff == EllipticGradient) + { + scanline[x] = qRgb(rcb - rSign * + (int)sqrt((xtable[0][x]*xtable[0][x] + + ytable[0][y]*ytable[0][y])*2.0), + gcb - gSign * + (int)sqrt((xtable[1][x]*xtable[1][x] + + ytable[1][y]*ytable[1][y])*2.0), + bcb - bSign * + (int)sqrt((xtable[2][x]*xtable[2][x] + + ytable[2][y]*ytable[2][y])*2.0)); + } + } + } + } + + if (ncols && (QPixmap::defaultDepth() < 15 )) { + if ( ncols < 2 || ncols > 256 ) + ncols = 3; + QColor *dPal = new QColor[ncols]; + for (int i=0; i<ncols; i++) { + dPal[i].setRgb ( rca + rDiff * i / ( ncols - 1 ), + gca + gDiff * i / ( ncols - 1 ), + bca + bDiff * i / ( ncols - 1 ) ); + } + dither(image, dPal, ncols); + delete [] dPal; + } + + delete [] xtable[0]; + delete [] xtable[1]; + delete [] xtable[2]; + delete [] ytable[0]; + delete [] ytable[1]; + delete [] ytable[2]; + + } + + return image; +} + +/** +Types for MMX and SSE packing of colors, for safe constraints +*/ +namespace { + +struct KIE4Pack +{ + Q_UINT16 data[4]; +}; + +struct KIE8Pack +{ + Q_UINT16 data[8]; +}; + +} + +//====================================================================== +// +// Intensity effects +// +//====================================================================== + + +/* This builds a 256 byte unsigned char lookup table with all + * the possible percent values prior to applying the effect, then uses + * integer math for the pixels. For any image larger than 9x9 this will be + * less expensive than doing a float operation on the 3 color components of + * each pixel. (mosfet) + */ +QImage& KImageEffect::intensity(QImage &image, float percent) +{ + if (image.width() == 0 || image.height() == 0) { +#ifndef NDEBUG + std::cerr << "WARNING: KImageEffect::intensity : invalid image\n"; +#endif + return image; + } + + int segColors = image.depth() > 8 ? 256 : image.numColors(); + int pixels = image.depth() > 8 ? image.width()*image.height() : + image.numColors(); + unsigned int *data = image.depth() > 8 ? (unsigned int *)image.bits() : + (unsigned int *)image.colorTable(); + + bool brighten = (percent >= 0); + if(percent < 0) + percent = -percent; + +#ifdef USE_MMX_INLINE_ASM + bool haveMMX = KCPUInfo::haveExtension( KCPUInfo::IntelMMX ); + + if(haveMMX) + { + Q_UINT16 p = Q_UINT16(256.0f*(percent)); + KIE4Pack mult = {{p,p,p,0}}; + + __asm__ __volatile__( + "pxor %%mm7, %%mm7\n\t" // zero mm7 for unpacking + "movq (%0), %%mm6\n\t" // copy intensity change to mm6 + : : "r"(&mult), "m"(mult)); + + unsigned int rem = pixels % 4; + pixels -= rem; + Q_UINT32 *end = ( data + pixels ); + + if (brighten) + { + while ( data != end ) { + __asm__ __volatile__( + "movq (%0), %%mm0\n\t" + "movq 8(%0), %%mm4\n\t" // copy 4 pixels of data to mm0 and mm4 + "movq %%mm0, %%mm1\n\t" + "movq %%mm0, %%mm3\n\t" + "movq %%mm4, %%mm5\n\t" // copy to registers for unpacking + "punpcklbw %%mm7, %%mm0\n\t" + "punpckhbw %%mm7, %%mm1\n\t" // unpack the two pixels from mm0 + "pmullw %%mm6, %%mm0\n\t" + "punpcklbw %%mm7, %%mm4\n\t" + "pmullw %%mm6, %%mm1\n\t" // multiply by intensity*256 + "psrlw $8, %%mm0\n\t" // divide by 256 + "pmullw %%mm6, %%mm4\n\t" + "psrlw $8, %%mm1\n\t" + "psrlw $8, %%mm4\n\t" + "packuswb %%mm1, %%mm0\n\t" // pack solution into mm0. saturates at 255 + "movq %%mm5, %%mm1\n\t" + + "punpckhbw %%mm7, %%mm1\n\t" // unpack 4th pixel in mm1 + + "pmullw %%mm6, %%mm1\n\t" + "paddusb %%mm3, %%mm0\n\t" // add intesity result to original of mm0 + "psrlw $8, %%mm1\n\t" + "packuswb %%mm1, %%mm4\n\t" // pack upper two pixels into mm4 + + "movq %%mm0, (%0)\n\t" // rewrite to memory lower two pixels + "paddusb %%mm5, %%mm4\n\t" + "movq %%mm4, 8(%0)\n\t" // rewrite upper two pixels + : : "r"(data) ); + data += 4; + } + + end += rem; + while ( data != end ) { + __asm__ __volatile__( + "movd (%0), %%mm0\n\t" // repeat above but for + "punpcklbw %%mm7, %%mm0\n\t" // one pixel at a time + "movq %%mm0, %%mm3\n\t" + "pmullw %%mm6, %%mm0\n\t" + "psrlw $8, %%mm0\n\t" + "paddw %%mm3, %%mm0\n\t" + "packuswb %%mm0, %%mm0\n\t" + "movd %%mm0, (%0)\n\t" + : : "r"(data) ); + data++; + } + } + else + { + while ( data != end ) { + __asm__ __volatile__( + "movq (%0), %%mm0\n\t" + "movq 8(%0), %%mm4\n\t" + "movq %%mm0, %%mm1\n\t" + "movq %%mm0, %%mm3\n\t" + + "movq %%mm4, %%mm5\n\t" + + "punpcklbw %%mm7, %%mm0\n\t" + "punpckhbw %%mm7, %%mm1\n\t" + "pmullw %%mm6, %%mm0\n\t" + "punpcklbw %%mm7, %%mm4\n\t" + "pmullw %%mm6, %%mm1\n\t" + "psrlw $8, %%mm0\n\t" + "pmullw %%mm6, %%mm4\n\t" + "psrlw $8, %%mm1\n\t" + "psrlw $8, %%mm4\n\t" + "packuswb %%mm1, %%mm0\n\t" + "movq %%mm5, %%mm1\n\t" + + "punpckhbw %%mm7, %%mm1\n\t" + + "pmullw %%mm6, %%mm1\n\t" + "psubusb %%mm0, %%mm3\n\t" // subtract darkening amount + "psrlw $8, %%mm1\n\t" + "packuswb %%mm1, %%mm4\n\t" + + "movq %%mm3, (%0)\n\t" + "psubusb %%mm4, %%mm5\n\t" // only change for this version is + "movq %%mm5, 8(%0)\n\t" // subtraction here as we are darkening image + : : "r"(data) ); + data += 4; + } + + end += rem; + while ( data != end ) { + __asm__ __volatile__( + "movd (%0), %%mm0\n\t" + "punpcklbw %%mm7, %%mm0\n\t" + "movq %%mm0, %%mm3\n\t" + "pmullw %%mm6, %%mm0\n\t" + "psrlw $8, %%mm0\n\t" + "psubusw %%mm0, %%mm3\n\t" + "packuswb %%mm3, %%mm3\n\t" + "movd %%mm3, (%0)\n\t" + : : "r"(data) ); + data++; + } + } + __asm__ __volatile__("emms"); // clear mmx state + } + else +#endif // USE_MMX_INLINE_ASM + { + unsigned char *segTbl = new unsigned char[segColors]; + int tmp; + if(brighten){ // keep overflow check out of loops + for(int i=0; i < segColors; ++i){ + tmp = (int)(i*percent); + if(tmp > 255) + tmp = 255; + segTbl[i] = tmp; + } + } + else{ + for(int i=0; i < segColors; ++i){ + tmp = (int)(i*percent); + if(tmp < 0) + tmp = 0; + segTbl[i] = tmp; + } + } + + if(brighten){ // same here + for(int i=0; i < pixels; ++i){ + int r = qRed(data[i]); + int g = qGreen(data[i]); + int b = qBlue(data[i]); + int a = qAlpha(data[i]); + r = r + segTbl[r] > 255 ? 255 : r + segTbl[r]; + g = g + segTbl[g] > 255 ? 255 : g + segTbl[g]; + b = b + segTbl[b] > 255 ? 255 : b + segTbl[b]; + data[i] = qRgba(r, g, b,a); + } + } + else{ + for(int i=0; i < pixels; ++i){ + int r = qRed(data[i]); + int g = qGreen(data[i]); + int b = qBlue(data[i]); + int a = qAlpha(data[i]); + r = r - segTbl[r] < 0 ? 0 : r - segTbl[r]; + g = g - segTbl[g] < 0 ? 0 : g - segTbl[g]; + b = b - segTbl[b] < 0 ? 0 : b - segTbl[b]; + data[i] = qRgba(r, g, b, a); + } + } + delete [] segTbl; + } + + return image; +} + +QImage& KImageEffect::channelIntensity(QImage &image, float percent, + RGBComponent channel) +{ + if (image.width() == 0 || image.height() == 0) { +#ifndef NDEBUG + std::cerr << "WARNING: KImageEffect::channelIntensity : invalid image\n"; +#endif + return image; + } + + int segColors = image.depth() > 8 ? 256 : image.numColors(); + unsigned char *segTbl = new unsigned char[segColors]; + int pixels = image.depth() > 8 ? image.width()*image.height() : + image.numColors(); + unsigned int *data = image.depth() > 8 ? (unsigned int *)image.bits() : + (unsigned int *)image.colorTable(); + bool brighten = (percent >= 0); + if(percent < 0) + percent = -percent; + + if(brighten){ // keep overflow check out of loops + for(int i=0; i < segColors; ++i){ + int tmp = (int)(i*percent); + if(tmp > 255) + tmp = 255; + segTbl[i] = tmp; + } + } + else{ + for(int i=0; i < segColors; ++i){ + int tmp = (int)(i*percent); + if(tmp < 0) + tmp = 0; + segTbl[i] = tmp; + } + } + + if(brighten){ // same here + if(channel == Red){ // and here ;-) + for(int i=0; i < pixels; ++i){ + int c = qRed(data[i]); + c = c + segTbl[c] > 255 ? 255 : c + segTbl[c]; + data[i] = qRgba(c, qGreen(data[i]), qBlue(data[i]), qAlpha(data[i])); + } + } + else if(channel == Green){ + for(int i=0; i < pixels; ++i){ + int c = qGreen(data[i]); + c = c + segTbl[c] > 255 ? 255 : c + segTbl[c]; + data[i] = qRgba(qRed(data[i]), c, qBlue(data[i]), qAlpha(data[i])); + } + } + else{ + for(int i=0; i < pixels; ++i){ + int c = qBlue(data[i]); + c = c + segTbl[c] > 255 ? 255 : c + segTbl[c]; + data[i] = qRgba(qRed(data[i]), qGreen(data[i]), c, qAlpha(data[i])); + } + } + + } + else{ + if(channel == Red){ + for(int i=0; i < pixels; ++i){ + int c = qRed(data[i]); + c = c - segTbl[c] < 0 ? 0 : c - segTbl[c]; + data[i] = qRgba(c, qGreen(data[i]), qBlue(data[i]), qAlpha(data[i])); + } + } + else if(channel == Green){ + for(int i=0; i < pixels; ++i){ + int c = qGreen(data[i]); + c = c - segTbl[c] < 0 ? 0 : c - segTbl[c]; + data[i] = qRgba(qRed(data[i]), c, qBlue(data[i]), qAlpha(data[i])); + } + } + else{ + for(int i=0; i < pixels; ++i){ + int c = qBlue(data[i]); + c = c - segTbl[c] < 0 ? 0 : c - segTbl[c]; + data[i] = qRgba(qRed(data[i]), qGreen(data[i]), c, qAlpha(data[i])); + } + } + } + delete [] segTbl; + + return image; +} + +// Modulate an image with an RBG channel of another image +// +QImage& KImageEffect::modulate(QImage &image, QImage &modImage, bool reverse, + ModulationType type, int factor, RGBComponent channel) +{ + if (image.width() == 0 || image.height() == 0 || + modImage.width() == 0 || modImage.height() == 0) { +#ifndef NDEBUG + std::cerr << "WARNING: KImageEffect::modulate : invalid image\n"; +#endif + return image; + } + + int r, g, b, h, s, v, a; + QColor clr; + int mod=0; + unsigned int x1, x2, y1, y2; + register int x, y; + + // for image, we handle only depth 32 + if (image.depth()<32) image = image.convertDepth(32); + + // for modImage, we handle depth 8 and 32 + if (modImage.depth()<8) modImage = modImage.convertDepth(8); + + unsigned int *colorTable2 = (modImage.depth()==8) ? + modImage.colorTable():0; + unsigned int *data1, *data2; + unsigned char *data2b; + unsigned int color1, color2; + + x1 = image.width(); y1 = image.height(); + x2 = modImage.width(); y2 = modImage.height(); + + for (y = 0; y < (int)y1; y++) { + data1 = (unsigned int *) image.scanLine(y); + data2 = (unsigned int *) modImage.scanLine( y%y2 ); + data2b = (unsigned char *) modImage.scanLine( y%y2 ); + + x=0; + while(x < (int)x1) { + color2 = (colorTable2) ? colorTable2[*data2b] : *data2; + if (reverse) { + color1 = color2; + color2 = *data1; + } + else + color1 = *data1; + + if (type == Intensity || type == Contrast) { + r = qRed(color1); + g = qGreen(color1); + b = qBlue(color1); + if (channel != All) { + mod = (channel == Red) ? qRed(color2) : + (channel == Green) ? qGreen(color2) : + (channel == Blue) ? qBlue(color2) : + (channel == Gray) ? qGray(color2) : 0; + mod = mod*factor/50; + } + + if (type == Intensity) { + if (channel == All) { + r += r * factor/50 * qRed(color2)/256; + g += g * factor/50 * qGreen(color2)/256; + b += b * factor/50 * qBlue(color2)/256; + } + else { + r += r * mod/256; + g += g * mod/256; + b += b * mod/256; + } + } + else { // Contrast + if (channel == All) { + r += (r-128) * factor/50 * qRed(color2)/128; + g += (g-128) * factor/50 * qGreen(color2)/128; + b += (b-128) * factor/50 * qBlue(color2)/128; + } + else { + r += (r-128) * mod/128; + g += (g-128) * mod/128; + b += (b-128) * mod/128; + } + } + + if (r<0) r=0; if (r>255) r=255; + if (g<0) g=0; if (g>255) g=255; + if (b<0) b=0; if (b>255) b=255; + a = qAlpha(*data1); + *data1 = qRgba(r, g, b, a); + } + else if (type == Saturation || type == HueShift) { + clr.setRgb(color1); + clr.hsv(&h, &s, &v); + mod = (channel == Red) ? qRed(color2) : + (channel == Green) ? qGreen(color2) : + (channel == Blue) ? qBlue(color2) : + (channel == Gray) ? qGray(color2) : 0; + mod = mod*factor/50; + + if (type == Saturation) { + s -= s * mod/256; + if (s<0) s=0; if (s>255) s=255; + } + else { // HueShift + h += mod; + while(h<0) h+=360; + h %= 360; + } + + clr.setHsv(h, s, v); + a = qAlpha(*data1); + *data1 = clr.rgb() | ((uint)(a & 0xff) << 24); + } + data1++; data2++; data2b++; x++; + if ( (x%x2) ==0) { data2 -= x2; data2b -= x2; } + } + } + return image; +} + + + +//====================================================================== +// +// Blend effects +// +//====================================================================== + + +// Nice and fast direct pixel manipulation +QImage& KImageEffect::blend(const QColor& clr, QImage& dst, float opacity) +{ + if (dst.width() <= 0 || dst.height() <= 0) + return dst; + + if (opacity < 0.0 || opacity > 1.0) { +#ifndef NDEBUG + std::cerr << "WARNING: KImageEffect::blend : invalid opacity. Range [0, 1]\n"; +#endif + return dst; + } + + if (dst.depth() != 32) + dst = dst.convertDepth(32); + + int pixels = dst.width() * dst.height(); + +#ifdef USE_SSE2_INLINE_ASM + if ( KCPUInfo::haveExtension( KCPUInfo::IntelSSE2 ) && pixels > 16 ) { + Q_UINT16 alpha = Q_UINT16( ( 1.0 - opacity ) * 256.0 ); + + KIE8Pack packedalpha = { { alpha, alpha, alpha, 256, + alpha, alpha, alpha, 256 } }; + + Q_UINT16 red = Q_UINT16( clr.red() * 256 * opacity ); + Q_UINT16 green = Q_UINT16( clr.green() * 256 * opacity ); + Q_UINT16 blue = Q_UINT16( clr.blue() * 256 * opacity ); + + KIE8Pack packedcolor = { { blue, green, red, 0, + blue, green, red, 0 } }; + + // Prepare the XMM5, XMM6 and XMM7 registers for unpacking and blending + __asm__ __volatile__( + "pxor %%xmm7, %%xmm7\n\t" // Zero out XMM7 for unpacking + "movdqu (%0), %%xmm6\n\t" // Set up (1 - alpha) * 256 in XMM6 + "movdqu (%1), %%xmm5\n\t" // Set up color * alpha * 256 in XMM5 + : : "r"(&packedalpha), "r"(&packedcolor), + "m"(packedcolor), "m"(packedalpha) ); + + Q_UINT32 *data = reinterpret_cast<Q_UINT32*>( dst.bits() ); + + // Check how many pixels we need to process to achieve 16 byte alignment + int offset = (16 - (Q_UINT32( data ) & 0x0f)) / 4; + + // The main loop processes 8 pixels / iteration + int remainder = (pixels - offset) % 8; + pixels -= remainder; + + // Alignment loop + for ( int i = 0; i < offset; i++ ) { + __asm__ __volatile__( + "movd (%0,%1,4), %%xmm0\n\t" // Load one pixel to XMM1 + "punpcklbw %%xmm7, %%xmm0\n\t" // Unpack the pixel + "pmullw %%xmm6, %%xmm0\n\t" // Multiply the pixel with (1 - alpha) * 256 + "paddw %%xmm5, %%xmm0\n\t" // Add color * alpha * 256 to the result + "psrlw $8, %%xmm0\n\t" // Divide by 256 + "packuswb %%xmm1, %%xmm0\n\t" // Pack the pixel to a dword + "movd %%xmm0, (%0,%1,4)\n\t" // Write the pixel to the image + : : "r"(data), "r"(i) ); + } + + // Main loop + for ( int i = offset; i < pixels; i += 8 ) { + __asm__ __volatile( + // Load 8 pixels to XMM registers 1 - 4 + "movq (%0,%1,4), %%xmm0\n\t" // Load pixels 1 and 2 to XMM1 + "movq 8(%0,%1,4), %%xmm1\n\t" // Load pixels 3 and 4 to XMM2 + "movq 16(%0,%1,4), %%xmm2\n\t" // Load pixels 5 and 6 to XMM3 + "movq 24(%0,%1,4), %%xmm3\n\t" // Load pixels 7 and 8 to XMM4 + + // Prefetch the pixels for next iteration + "prefetchnta 32(%0,%1,4) \n\t" + + // Blend pixels 1 and 2 + "punpcklbw %%xmm7, %%xmm0\n\t" // Unpack the pixels + "pmullw %%xmm6, %%xmm0\n\t" // Multiply the pixels with (1 - alpha) * 256 + "paddw %%xmm5, %%xmm0\n\t" // Add color * alpha * 256 to the result + "psrlw $8, %%xmm0\n\t" // Divide by 256 + + // Blend pixels 3 and 4 + "punpcklbw %%xmm7, %%xmm1\n\t" // Unpack the pixels + "pmullw %%xmm6, %%xmm1\n\t" // Multiply the pixels with (1 - alpha) * 256 + "paddw %%xmm5, %%xmm1\n\t" // Add color * alpha * 256 to the result + "psrlw $8, %%xmm1\n\t" // Divide by 256 + + // Blend pixels 5 and 6 + "punpcklbw %%xmm7, %%xmm2\n\t" // Unpack the pixels + "pmullw %%xmm6, %%xmm2\n\t" // Multiply the pixels with (1 - alpha) * 256 + "paddw %%xmm5, %%xmm2\n\t" // Add color * alpha * 256 to the result + "psrlw $8, %%xmm2\n\t" // Divide by 256 + + // Blend pixels 7 and 8 + "punpcklbw %%xmm7, %%xmm3\n\t" // Unpack the pixels + "pmullw %%xmm6, %%xmm3\n\t" // Multiply the pixels with (1 - alpha) * 256 + "paddw %%xmm5, %%xmm3\n\t" // Add color * alpha * 256 to the result + "psrlw $8, %%xmm3\n\t" // Divide by 256 + + // Pack the pixels into 2 double quadwords + "packuswb %%xmm1, %%xmm0\n\t" // Pack pixels 1 - 4 to a double qword + "packuswb %%xmm3, %%xmm2\n\t" // Pack pixles 5 - 8 to a double qword + + // Write the pixels back to the image + "movdqa %%xmm0, (%0,%1,4)\n\t" // Store pixels 1 - 4 + "movdqa %%xmm2, 16(%0,%1,4)\n\t" // Store pixels 5 - 8 + : : "r"(data), "r"(i) ); + } + + // Cleanup loop + for ( int i = pixels; i < pixels + remainder; i++ ) { + __asm__ __volatile__( + "movd (%0,%1,4), %%xmm0\n\t" // Load one pixel to XMM1 + "punpcklbw %%xmm7, %%xmm0\n\t" // Unpack the pixel + "pmullw %%xmm6, %%xmm0\n\t" // Multiply the pixel with (1 - alpha) * 256 + "paddw %%xmm5, %%xmm0\n\t" // Add color * alpha * 256 to the result + "psrlw $8, %%xmm0\n\t" // Divide by 256 + "packuswb %%xmm1, %%xmm0\n\t" // Pack the pixel to a dword + "movd %%xmm0, (%0,%1,4)\n\t" // Write the pixel to the image + : : "r"(data), "r"(i) ); + } + } else +#endif + +#ifdef USE_MMX_INLINE_ASM + if ( KCPUInfo::haveExtension( KCPUInfo::IntelMMX ) && pixels > 1 ) { + Q_UINT16 alpha = Q_UINT16( ( 1.0 - opacity ) * 256.0 ); + KIE4Pack packedalpha = { { alpha, alpha, alpha, 256 } }; + + Q_UINT16 red = Q_UINT16( clr.red() * 256 * opacity ); + Q_UINT16 green = Q_UINT16( clr.green() * 256 * opacity ); + Q_UINT16 blue = Q_UINT16( clr.blue() * 256 * opacity ); + + KIE4Pack packedcolor = { { blue, green, red, 0 } }; + + __asm__ __volatile__( + "pxor %%mm7, %%mm7\n\t" // Zero out MM7 for unpacking + "movq (%0), %%mm6\n\t" // Set up (1 - alpha) * 256 in MM6 + "movq (%1), %%mm5\n\t" // Set up color * alpha * 256 in MM5 + : : "r"(&packedalpha), "r"(&packedcolor), "m"(packedcolor), "m"(packedalpha) ); + + Q_UINT32 *data = reinterpret_cast<Q_UINT32*>( dst.bits() ); + + // The main loop processes 4 pixels / iteration + int remainder = pixels % 4; + pixels -= remainder; + + // Main loop + for ( int i = 0; i < pixels; i += 4 ) { + __asm__ __volatile__( + // Load 4 pixels to MM registers 1 - 4 + "movd (%0,%1,4), %%mm0\n\t" // Load the 1st pixel to MM0 + "movd 4(%0,%1,4), %%mm1\n\t" // Load the 2nd pixel to MM1 + "movd 8(%0,%1,4), %%mm2\n\t" // Load the 3rd pixel to MM2 + "movd 12(%0,%1,4), %%mm3\n\t" // Load the 4th pixel to MM3 + + // Blend the first pixel + "punpcklbw %%mm7, %%mm0\n\t" // Unpack the pixel + "pmullw %%mm6, %%mm0\n\t" // Multiply the pixel with (1 - alpha) * 256 + "paddw %%mm5, %%mm0\n\t" // Add color * alpha * 256 to the result + "psrlw $8, %%mm0\n\t" // Divide by 256 + + // Blend the second pixel + "punpcklbw %%mm7, %%mm1\n\t" // Unpack the pixel + "pmullw %%mm6, %%mm1\n\t" // Multiply the pixel with (1 - alpha) * 256 + "paddw %%mm5, %%mm1\n\t" // Add color * alpha * 256 to the result + "psrlw $8, %%mm1\n\t" // Divide by 256 + + // Blend the third pixel + "punpcklbw %%mm7, %%mm2\n\t" // Unpack the pixel + "pmullw %%mm6, %%mm2\n\t" // Multiply the pixel with (1 - alpha) * 256 + "paddw %%mm5, %%mm2\n\t" // Add color * alpha * 256 to the result + "psrlw $8, %%mm2\n\t" // Divide by 256 + + // Blend the fourth pixel + "punpcklbw %%mm7, %%mm3\n\t" // Unpack the pixel + "pmullw %%mm6, %%mm3\n\t" // Multiply the pixel with (1 - alpha) * 256 + "paddw %%mm5, %%mm3\n\t" // Add color * alpha * 256 to the result + "psrlw $8, %%mm3\n\t" // Divide by 256 + + // Pack the pixels into 2 quadwords + "packuswb %%mm1, %%mm0\n\t" // Pack pixels 1 and 2 to a qword + "packuswb %%mm3, %%mm2\n\t" // Pack pixels 3 and 4 to a qword + + // Write the pixels back to the image + "movq %%mm0, (%0,%1,4)\n\t" // Store pixels 1 and 2 + "movq %%mm2, 8(%0,%1,4)\n\t" // Store pixels 3 and 4 + : : "r"(data), "r"(i) ); + } + + // Cleanup loop + for ( int i = pixels; i < pixels + remainder; i++ ) { + __asm__ __volatile__( + "movd (%0,%1,4), %%mm0\n\t" // Load one pixel to MM1 + "punpcklbw %%mm7, %%mm0\n\t" // Unpack the pixel + "pmullw %%mm6, %%mm0\n\t" // Multiply the pixel with 1 - alpha * 256 + "paddw %%mm5, %%mm0\n\t" // Add color * alpha * 256 to the result + "psrlw $8, %%mm0\n\t" // Divide by 256 + "packuswb %%mm0, %%mm0\n\t" // Pack the pixel to a dword + "movd %%mm0, (%0,%1,4)\n\t" // Write the pixel to the image + : : "r"(data), "r"(i) ); + } + + // Empty the MMX state + __asm__ __volatile__("emms"); + } else +#endif // USE_MMX_INLINE_ASM + + { + int rcol, gcol, bcol; + clr.rgb(&rcol, &gcol, &bcol); + +#ifdef WORDS_BIGENDIAN // ARGB (skip alpha) + register unsigned char *data = (unsigned char *)dst.bits() + 1; +#else // BGRA + register unsigned char *data = (unsigned char *)dst.bits(); +#endif + + for (register int i=0; i<pixels; i++) + { +#ifdef WORDS_BIGENDIAN + *data += (unsigned char)((rcol - *data) * opacity); + data++; + *data += (unsigned char)((gcol - *data) * opacity); + data++; + *data += (unsigned char)((bcol - *data) * opacity); + data++; +#else + *data += (unsigned char)((bcol - *data) * opacity); + data++; + *data += (unsigned char)((gcol - *data) * opacity); + data++; + *data += (unsigned char)((rcol - *data) * opacity); + data++; +#endif + data++; // skip alpha + } + } + + return dst; +} + +// Nice and fast direct pixel manipulation +QImage& KImageEffect::blend(QImage& src, QImage& dst, float opacity) +{ + if (src.width() <= 0 || src.height() <= 0) + return dst; + if (dst.width() <= 0 || dst.height() <= 0) + return dst; + + if (src.width() != dst.width() || src.height() != dst.height()) { +#ifndef NDEBUG + std::cerr << "WARNING: KImageEffect::blend : src and destination images are not the same size\n"; +#endif + return dst; + } + + if (opacity < 0.0 || opacity > 1.0) { +#ifndef NDEBUG + std::cerr << "WARNING: KImageEffect::blend : invalid opacity. Range [0, 1]\n"; +#endif + return dst; + } + + if (src.depth() != 32) src = src.convertDepth(32); + if (dst.depth() != 32) dst = dst.convertDepth(32); + + int pixels = src.width() * src.height(); + +#ifdef USE_SSE2_INLINE_ASM + if ( KCPUInfo::haveExtension( KCPUInfo::IntelSSE2 ) && pixels > 16 ) { + Q_UINT16 alpha = Q_UINT16( opacity * 256.0 ); + KIE8Pack packedalpha = { { alpha, alpha, alpha, 0, + alpha, alpha, alpha, 0 } }; + + // Prepare the XMM6 and XMM7 registers for unpacking and blending + __asm__ __volatile__( + "pxor %%xmm7, %%xmm7\n\t" // Zero out XMM7 for unpacking + "movdqu (%0), %%xmm6\n\t" // Set up alpha * 256 in XMM6 + : : "r"(&packedalpha), "m"(packedalpha) ); + + Q_UINT32 *data1 = reinterpret_cast<Q_UINT32*>( src.bits() ); + Q_UINT32 *data2 = reinterpret_cast<Q_UINT32*>( dst.bits() ); + + // Check how many pixels we need to process to achieve 16 byte alignment + int offset = (16 - (Q_UINT32( data2 ) & 0x0f)) / 4; + + // The main loop processes 4 pixels / iteration + int remainder = (pixels - offset) % 4; + pixels -= remainder; + + // Alignment loop + for ( int i = 0; i < offset; i++ ) { + __asm__ __volatile__( + "movd (%1,%2,4), %%xmm1\n\t" // Load one dst pixel to XMM1 + "punpcklbw %%xmm7, %%xmm1\n\t" // Unpack the pixel + "movd (%0,%2,4), %%xmm0\n\t" // Load one src pixel to XMM0 + "punpcklbw %%xmm7, %%xmm0\n\t" // Unpack the pixel + "psubw %%xmm1, %%xmm0\n\t" // Subtract dst from src + "pmullw %%xmm6, %%xmm0\n\t" // Multiply the result with alpha * 256 + "psllw $8, %%xmm1\n\t" // Multiply dst with 256 + "paddw %%xmm1, %%xmm0\n\t" // Add dst to result + "psrlw $8, %%xmm0\n\t" // Divide by 256 + "packuswb %%xmm1, %%xmm0\n\t" // Pack the pixel to a dword + "movd %%xmm0, (%1,%2,4)\n\t" // Write the pixel to the image + : : "r"(data1), "r"(data2), "r"(i) ); + } + + // Main loop + for ( int i = offset; i < pixels; i += 4 ) { + __asm__ __volatile__( + // Load 4 src pixels to XMM0 and XMM2 and 4 dst pixels to XMM1 and XMM3 + "movq (%0,%2,4), %%xmm0\n\t" // Load two src pixels to XMM0 + "movq (%1,%2,4), %%xmm1\n\t" // Load two dst pixels to XMM1 + "movq 8(%0,%2,4), %%xmm2\n\t" // Load two src pixels to XMM2 + "movq 8(%1,%2,4), %%xmm3\n\t" // Load two dst pixels to XMM3 + + // Prefetch the pixels for the iteration after the next one + "prefetchnta 32(%0,%2,4) \n\t" + "prefetchnta 32(%1,%2,4) \n\t" + + // Blend the first two pixels + "punpcklbw %%xmm7, %%xmm1\n\t" // Unpack the dst pixels + "punpcklbw %%xmm7, %%xmm0\n\t" // Unpack the src pixels + "psubw %%xmm1, %%xmm0\n\t" // Subtract dst from src + "pmullw %%xmm6, %%xmm0\n\t" // Multiply the result with alpha * 256 + "psllw $8, %%xmm1\n\t" // Multiply dst with 256 + "paddw %%xmm1, %%xmm0\n\t" // Add dst to the result + "psrlw $8, %%xmm0\n\t" // Divide by 256 + + // Blend the next two pixels + "punpcklbw %%xmm7, %%xmm3\n\t" // Unpack the dst pixels + "punpcklbw %%xmm7, %%xmm2\n\t" // Unpack the src pixels + "psubw %%xmm3, %%xmm2\n\t" // Subtract dst from src + "pmullw %%xmm6, %%xmm2\n\t" // Multiply the result with alpha * 256 + "psllw $8, %%xmm3\n\t" // Multiply dst with 256 + "paddw %%xmm3, %%xmm2\n\t" // Add dst to the result + "psrlw $8, %%xmm2\n\t" // Divide by 256 + + // Write the pixels back to the image + "packuswb %%xmm2, %%xmm0\n\t" // Pack the pixels to a double qword + "movdqa %%xmm0, (%1,%2,4)\n\t" // Store the pixels + : : "r"(data1), "r"(data2), "r"(i) ); + } + + // Cleanup loop + for ( int i = pixels; i < pixels + remainder; i++ ) { + __asm__ __volatile__( + "movd (%1,%2,4), %%xmm1\n\t" // Load one dst pixel to XMM1 + "punpcklbw %%xmm7, %%xmm1\n\t" // Unpack the pixel + "movd (%0,%2,4), %%xmm0\n\t" // Load one src pixel to XMM0 + "punpcklbw %%xmm7, %%xmm0\n\t" // Unpack the pixel + "psubw %%xmm1, %%xmm0\n\t" // Subtract dst from src + "pmullw %%xmm6, %%xmm0\n\t" // Multiply the result with alpha * 256 + "psllw $8, %%xmm1\n\t" // Multiply dst with 256 + "paddw %%xmm1, %%xmm0\n\t" // Add dst to result + "psrlw $8, %%xmm0\n\t" // Divide by 256 + "packuswb %%xmm1, %%xmm0\n\t" // Pack the pixel to a dword + "movd %%xmm0, (%1,%2,4)\n\t" // Write the pixel to the image + : : "r"(data1), "r"(data2), "r"(i) ); + } + } else +#endif // USE_SSE2_INLINE_ASM + +#ifdef USE_MMX_INLINE_ASM + if ( KCPUInfo::haveExtension( KCPUInfo::IntelMMX ) && pixels > 1 ) { + Q_UINT16 alpha = Q_UINT16( opacity * 256.0 ); + KIE4Pack packedalpha = { { alpha, alpha, alpha, 0 } }; + + // Prepare the MM6 and MM7 registers for blending and unpacking + __asm__ __volatile__( + "pxor %%mm7, %%mm7\n\t" // Zero out MM7 for unpacking + "movq (%0), %%mm6\n\t" // Set up alpha * 256 in MM6 + : : "r"(&packedalpha), "m"(packedalpha) ); + + Q_UINT32 *data1 = reinterpret_cast<Q_UINT32*>( src.bits() ); + Q_UINT32 *data2 = reinterpret_cast<Q_UINT32*>( dst.bits() ); + + // The main loop processes 2 pixels / iteration + int remainder = pixels % 2; + pixels -= remainder; + + // Main loop + for ( int i = 0; i < pixels; i += 2 ) { + __asm__ __volatile__( + // Load 2 src pixels to MM0 and MM2 and 2 dst pixels to MM1 and MM3 + "movd (%0,%2,4), %%mm0\n\t" // Load the 1st src pixel to MM0 + "movd (%1,%2,4), %%mm1\n\t" // Load the 1st dst pixel to MM1 + "movd 4(%0,%2,4), %%mm2\n\t" // Load the 2nd src pixel to MM2 + "movd 4(%1,%2,4), %%mm3\n\t" // Load the 2nd dst pixel to MM3 + + // Blend the first pixel + "punpcklbw %%mm7, %%mm0\n\t" // Unpack the src pixel + "punpcklbw %%mm7, %%mm1\n\t" // Unpack the dst pixel + "psubw %%mm1, %%mm0\n\t" // Subtract dst from src + "pmullw %%mm6, %%mm0\n\t" // Multiply the result with alpha * 256 + "psllw $8, %%mm1\n\t" // Multiply dst with 256 + "paddw %%mm1, %%mm0\n\t" // Add dst to the result + "psrlw $8, %%mm0\n\t" // Divide by 256 + + // Blend the second pixel + "punpcklbw %%mm7, %%mm2\n\t" // Unpack the src pixel + "punpcklbw %%mm7, %%mm3\n\t" // Unpack the dst pixel + "psubw %%mm3, %%mm2\n\t" // Subtract dst from src + "pmullw %%mm6, %%mm2\n\t" // Multiply the result with alpha * 256 + "psllw $8, %%mm3\n\t" // Multiply dst with 256 + "paddw %%mm3, %%mm2\n\t" // Add dst to the result + "psrlw $8, %%mm2\n\t" // Divide by 256 + + // Write the pixels back to the image + "packuswb %%mm2, %%mm0\n\t" // Pack the pixels to a qword + "movq %%mm0, (%1,%2,4)\n\t" // Store the pixels + : : "r"(data1), "r"(data2), "r"(i) ); + } + + // Blend the remaining pixel (if there is one) + if ( remainder ) { + __asm__ __volatile__( + "movd (%0), %%mm0\n\t" // Load one src pixel to MM0 + "punpcklbw %%mm7, %%mm0\n\t" // Unpack the src pixel + "movd (%1), %%mm1\n\t" // Load one dst pixel to MM1 + "punpcklbw %%mm7, %%mm1\n\t" // Unpack the dst pixel + "psubw %%mm1, %%mm0\n\t" // Subtract dst from src + "pmullw %%mm6, %%mm0\n\t" // Multiply the result with alpha * 256 + "psllw $8, %%mm1\n\t" // Multiply dst with 256 + "paddw %%mm1, %%mm0\n\t" // Add dst to result + "psrlw $8, %%mm0\n\t" // Divide by 256 + "packuswb %%mm0, %%mm0\n\t" // Pack the pixel to a dword + "movd %%mm0, (%1)\n\t" // Write the pixel to the image + : : "r"(data1 + pixels), "r"(data2 + pixels) ); + } + + // Empty the MMX state + __asm__ __volatile__("emms"); + } else +#endif // USE_MMX_INLINE_ASM + + { +#ifdef WORDS_BIGENDIAN // ARGB (skip alpha) + register unsigned char *data1 = (unsigned char *)dst.bits() + 1; + register unsigned char *data2 = (unsigned char *)src.bits() + 1; +#else // BGRA + register unsigned char *data1 = (unsigned char *)dst.bits(); + register unsigned char *data2 = (unsigned char *)src.bits(); +#endif + + for (register int i=0; i<pixels; i++) + { +#ifdef WORDS_BIGENDIAN + *data1 += (unsigned char)((*(data2++) - *data1) * opacity); + data1++; + *data1 += (unsigned char)((*(data2++) - *data1) * opacity); + data1++; + *data1 += (unsigned char)((*(data2++) - *data1) * opacity); + data1++; +#else + *data1 += (unsigned char)((*(data2++) - *data1) * opacity); + data1++; + *data1 += (unsigned char)((*(data2++) - *data1) * opacity); + data1++; + *data1 += (unsigned char)((*(data2++) - *data1) * opacity); + data1++; +#endif + data1++; // skip alpha + data2++; + } + } + + return dst; +} + + +QImage& KImageEffect::blend(QImage &image, float initial_intensity, + const QColor &bgnd, GradientType eff, + bool anti_dir) +{ + if (image.width() == 0 || image.height() == 0 || image.depth()!=32 ) { +#ifndef NDEBUG + std::cerr << "WARNING: KImageEffect::blend : invalid image\n"; +#endif + return image; + } + + int r_bgnd = bgnd.red(), g_bgnd = bgnd.green(), b_bgnd = bgnd.blue(); + int r, g, b; + int ind; + + unsigned int xi, xf, yi, yf; + unsigned int a; + + // check the boundaries of the initial intesity param + float unaffected = 1; + if (initial_intensity > 1) initial_intensity = 1; + if (initial_intensity < -1) initial_intensity = -1; + if (initial_intensity < 0) { + unaffected = 1. + initial_intensity; + initial_intensity = 0; + } + + + float intensity = initial_intensity; + float var = 1. - initial_intensity; + + if (anti_dir) { + initial_intensity = intensity = 1.; + var = -var; + } + + register int x, y; + + unsigned int *data = (unsigned int *)image.bits(); + + int image_width = image.width(); //Those can't change + int image_height = image.height(); + + + if( eff == VerticalGradient || eff == HorizontalGradient ) { + + // set the image domain to apply the effect to + xi = 0, xf = image_width; + yi = 0, yf = image_height; + if (eff == VerticalGradient) { + if (anti_dir) yf = (int)(image_height * unaffected); + else yi = (int)(image_height * (1 - unaffected)); + } + else { + if (anti_dir) xf = (int)(image_width * unaffected); + else xi = (int)(image_height * (1 - unaffected)); + } + + var /= (eff == VerticalGradient?yf-yi:xf-xi); + + int ind_base; + for (y = yi; y < (int)yf; y++) { + intensity = eff == VerticalGradient? intensity + var : + initial_intensity; + ind_base = image_width * y ; + for (x = xi; x < (int)xf ; x++) { + if (eff == HorizontalGradient) intensity += var; + ind = x + ind_base; + r = qRed (data[ind]) + (int)(intensity * + (r_bgnd - qRed (data[ind]))); + g = qGreen(data[ind]) + (int)(intensity * + (g_bgnd - qGreen(data[ind]))); + b = qBlue (data[ind]) + (int)(intensity * + (b_bgnd - qBlue (data[ind]))); + if (r > 255) r = 255; if (r < 0 ) r = 0; + if (g > 255) g = 255; if (g < 0 ) g = 0; + if (b > 255) b = 255; if (b < 0 ) b = 0; + a = qAlpha(data[ind]); + data[ind] = qRgba(r, g, b, a); + } + } + } + else if (eff == DiagonalGradient || eff == CrossDiagonalGradient) { + float xvar = var / 2 / image_width; // / unaffected; + float yvar = var / 2 / image_height; // / unaffected; + float tmp; + + for (x = 0; x < image_width ; x++) { + tmp = xvar * (eff == DiagonalGradient? x : image.width()-x-1); + ind = x; + for (y = 0; y < image_height ; y++) { + intensity = initial_intensity + tmp + yvar * y; + + r = qRed (data[ind]) + (int)(intensity * + (r_bgnd - qRed (data[ind]))); + g = qGreen(data[ind]) + (int)(intensity * + (g_bgnd - qGreen(data[ind]))); + b = qBlue (data[ind]) + (int)(intensity * + (b_bgnd - qBlue (data[ind]))); + if (r > 255) r = 255; if (r < 0 ) r = 0; + if (g > 255) g = 255; if (g < 0 ) g = 0; + if (b > 255) b = 255; if (b < 0 ) b = 0; + a = qAlpha(data[ind]); + data[ind] = qRgba(r, g, b, a); + + ind += image_width; + } + } + } + + else if (eff == RectangleGradient || eff == EllipticGradient) { + float xvar; + float yvar; + + for (x = 0; x < image_width / 2 + image_width % 2; x++) { + xvar = var / image_width * (image_width - x*2/unaffected-1); + for (y = 0; y < image_height / 2 + image_height % 2; y++) { + yvar = var / image_height * (image_height - y*2/unaffected -1); + + if (eff == RectangleGradient) + intensity = initial_intensity + QMAX(xvar, yvar); + else + intensity = initial_intensity + sqrt(xvar * xvar + yvar * yvar); + if (intensity > 1) intensity = 1; + if (intensity < 0) intensity = 0; + + //NW + ind = x + image_width * y ; + r = qRed (data[ind]) + (int)(intensity * + (r_bgnd - qRed (data[ind]))); + g = qGreen(data[ind]) + (int)(intensity * + (g_bgnd - qGreen(data[ind]))); + b = qBlue (data[ind]) + (int)(intensity * + (b_bgnd - qBlue (data[ind]))); + if (r > 255) r = 255; if (r < 0 ) r = 0; + if (g > 255) g = 255; if (g < 0 ) g = 0; + if (b > 255) b = 255; if (b < 0 ) b = 0; + a = qAlpha(data[ind]); + data[ind] = qRgba(r, g, b, a); + + //NE + ind = image_width - x - 1 + image_width * y ; + r = qRed (data[ind]) + (int)(intensity * + (r_bgnd - qRed (data[ind]))); + g = qGreen(data[ind]) + (int)(intensity * + (g_bgnd - qGreen(data[ind]))); + b = qBlue (data[ind]) + (int)(intensity * + (b_bgnd - qBlue (data[ind]))); + if (r > 255) r = 255; if (r < 0 ) r = 0; + if (g > 255) g = 255; if (g < 0 ) g = 0; + if (b > 255) b = 255; if (b < 0 ) b = 0; + a = qAlpha(data[ind]); + data[ind] = qRgba(r, g, b, a); + } + } + + //CT loop is doubled because of stupid central row/column issue. + // other solution? + for (x = 0; x < image_width / 2; x++) { + xvar = var / image_width * (image_width - x*2/unaffected-1); + for (y = 0; y < image_height / 2; y++) { + yvar = var / image_height * (image_height - y*2/unaffected -1); + + if (eff == RectangleGradient) + intensity = initial_intensity + QMAX(xvar, yvar); + else + intensity = initial_intensity + sqrt(xvar * xvar + yvar * yvar); + if (intensity > 1) intensity = 1; + if (intensity < 0) intensity = 0; + + //SW + ind = x + image_width * (image_height - y -1) ; + r = qRed (data[ind]) + (int)(intensity * + (r_bgnd - qRed (data[ind]))); + g = qGreen(data[ind]) + (int)(intensity * + (g_bgnd - qGreen(data[ind]))); + b = qBlue (data[ind]) + (int)(intensity * + (b_bgnd - qBlue (data[ind]))); + if (r > 255) r = 255; if (r < 0 ) r = 0; + if (g > 255) g = 255; if (g < 0 ) g = 0; + if (b > 255) b = 255; if (b < 0 ) b = 0; + a = qAlpha(data[ind]); + data[ind] = qRgba(r, g, b, a); + + //SE + ind = image_width-x-1 + image_width * (image_height - y - 1) ; + r = qRed (data[ind]) + (int)(intensity * + (r_bgnd - qRed (data[ind]))); + g = qGreen(data[ind]) + (int)(intensity * + (g_bgnd - qGreen(data[ind]))); + b = qBlue (data[ind]) + (int)(intensity * + (b_bgnd - qBlue (data[ind]))); + if (r > 255) r = 255; if (r < 0 ) r = 0; + if (g > 255) g = 255; if (g < 0 ) g = 0; + if (b > 255) b = 255; if (b < 0 ) b = 0; + a = qAlpha(data[ind]); + data[ind] = qRgba(r, g, b, a); + } + } + } +#ifndef NDEBUG + else std::cerr << "KImageEffect::blend effect not implemented" << std::endl; +#endif + return image; +} + +// Not very efficient as we create a third big image... +// +QImage& KImageEffect::blend(QImage &image1, QImage &image2, + GradientType gt, int xf, int yf) +{ + if (image1.width() == 0 || image1.height() == 0 || + image2.width() == 0 || image2.height() == 0) + return image1; + + QImage image3; + + image3 = KImageEffect::unbalancedGradient(image1.size(), + QColor(0,0,0), QColor(255,255,255), + gt, xf, yf, 0); + + return blend(image1,image2,image3, Red); // Channel to use is arbitrary +} + +// Blend image2 into image1, using an RBG channel of blendImage +// +QImage& KImageEffect::blend(QImage &image1, QImage &image2, + QImage &blendImage, RGBComponent channel) +{ + if (image1.width() == 0 || image1.height() == 0 || + image2.width() == 0 || image2.height() == 0 || + blendImage.width() == 0 || blendImage.height() == 0) { +#ifndef NDEBUG + std::cerr << "KImageEffect::blend effect invalid image" << std::endl; +#endif + return image1; + } + + int r, g, b; + int ind1, ind2, ind3; + + unsigned int x1, x2, x3, y1, y2, y3; + unsigned int a; + + register int x, y; + + // for image1 and image2, we only handle depth 32 + if (image1.depth()<32) image1 = image1.convertDepth(32); + if (image2.depth()<32) image2 = image2.convertDepth(32); + + // for blendImage, we handle depth 8 and 32 + if (blendImage.depth()<8) blendImage = blendImage.convertDepth(8); + + unsigned int *colorTable3 = (blendImage.depth()==8) ? + blendImage.colorTable():0; + + unsigned int *data1 = (unsigned int *)image1.bits(); + unsigned int *data2 = (unsigned int *)image2.bits(); + unsigned int *data3 = (unsigned int *)blendImage.bits(); + unsigned char *data3b = (unsigned char *)blendImage.bits(); + unsigned int color3; + + x1 = image1.width(); y1 = image1.height(); + x2 = image2.width(); y2 = image2.height(); + x3 = blendImage.width(); y3 = blendImage.height(); + + for (y = 0; y < (int)y1; y++) { + ind1 = x1*y; + ind2 = x2*(y%y2); + ind3 = x3*(y%y3); + + x=0; + while(x < (int)x1) { + color3 = (colorTable3) ? colorTable3[data3b[ind3]] : data3[ind3]; + + a = (channel == Red) ? qRed(color3) : + (channel == Green) ? qGreen(color3) : + (channel == Blue) ? qBlue(color3) : qGray(color3); + + r = (a*qRed(data1[ind1]) + (256-a)*qRed(data2[ind2]))/256; + g = (a*qGreen(data1[ind1]) + (256-a)*qGreen(data2[ind2]))/256; + b = (a*qBlue(data1[ind1]) + (256-a)*qBlue(data2[ind2]))/256; + + a = qAlpha(data1[ind1]); + data1[ind1] = qRgba(r, g, b, a); + + ind1++; ind2++; ind3++; x++; + if ( (x%x2) ==0) ind2 -= x2; + if ( (x%x3) ==0) ind3 -= x3; + } + } + return image1; +} + + +//====================================================================== +// +// Hash effects +// +//====================================================================== + +unsigned int KImageEffect::lHash(unsigned int c) +{ + unsigned char r = qRed(c), g = qGreen(c), b = qBlue(c), a = qAlpha(c); + unsigned char nr, ng, nb; + nr =(r >> 1) + (r >> 2); nr = nr > r ? 0 : nr; + ng =(g >> 1) + (g >> 2); ng = ng > g ? 0 : ng; + nb =(b >> 1) + (b >> 2); nb = nb > b ? 0 : nb; + + return qRgba(nr, ng, nb, a); +} + + +// ----------------------------------------------------------------------------- + +unsigned int KImageEffect::uHash(unsigned int c) +{ + unsigned char r = qRed(c), g = qGreen(c), b = qBlue(c), a = qAlpha(c); + unsigned char nr, ng, nb; + nr = r + (r >> 3); nr = nr < r ? ~0 : nr; + ng = g + (g >> 3); ng = ng < g ? ~0 : ng; + nb = b + (b >> 3); nb = nb < b ? ~0 : nb; + + return qRgba(nr, ng, nb, a); +} + + +// ----------------------------------------------------------------------------- + +QImage& KImageEffect::hash(QImage &image, Lighting lite, unsigned int spacing) +{ + if (image.width() == 0 || image.height() == 0) { +#ifndef NDEBUG + std::cerr << "KImageEffect::hash effect invalid image" << std::endl; +#endif + return image; + } + + register int x, y; + unsigned int *data = (unsigned int *)image.bits(); + unsigned int ind; + + //CT no need to do it if not enough space + if ((lite == NorthLite || + lite == SouthLite)&& + (unsigned)image.height() < 2+spacing) return image; + if ((lite == EastLite || + lite == WestLite)&& + (unsigned)image.height() < 2+spacing) return image; + + if (lite == NorthLite || lite == SouthLite) { + for (y = 0 ; y < image.height(); y = y + 2 + spacing) { + for (x = 0; x < image.width(); x++) { + ind = x + image.width() * y; + data[ind] = lite==NorthLite?uHash(data[ind]):lHash(data[ind]); + + ind = ind + image.width(); + data[ind] = lite==NorthLite?lHash(data[ind]):uHash(data[ind]); + } + } + } + + else if (lite == EastLite || lite == WestLite) { + for (y = 0 ; y < image.height(); y++) { + for (x = 0; x < image.width(); x = x + 2 + spacing) { + ind = x + image.width() * y; + data[ind] = lite==EastLite?uHash(data[ind]):lHash(data[ind]); + + ind++; + data[ind] = lite==EastLite?lHash(data[ind]):uHash(data[ind]); + } + } + } + + else if (lite == NWLite || lite == SELite) { + for (y = 0 ; y < image.height(); y++) { + for (x = 0; + x < (int)(image.width() - ((y & 1)? 1 : 0) * spacing); + x = x + 2 + spacing) { + ind = x + image.width() * y + ((y & 1)? 1 : 0); + data[ind] = lite==NWLite?uHash(data[ind]):lHash(data[ind]); + + ind++; + data[ind] = lite==NWLite?lHash(data[ind]):uHash(data[ind]); + } + } + } + + else if (lite == SWLite || lite == NELite) { + for (y = 0 ; y < image.height(); y++) { + for (x = 0 + ((y & 1)? 1 : 0); x < image.width(); x = x + 2 + spacing) { + ind = x + image.width() * y - ((y & 1)? 1 : 0); + data[ind] = lite==SWLite?uHash(data[ind]):lHash(data[ind]); + + ind++; + data[ind] = lite==SWLite?lHash(data[ind]):uHash(data[ind]); + } + } + } + + return image; +} + + +//====================================================================== +// +// Flatten effects +// +//====================================================================== + +QImage& KImageEffect::flatten(QImage &img, const QColor &ca, + const QColor &cb, int ncols) +{ + if (img.width() == 0 || img.height() == 0) + return img; + + // a bitmap is easy... + if (img.depth() == 1) { + img.setColor(0, ca.rgb()); + img.setColor(1, cb.rgb()); + return img; + } + + int r1 = ca.red(); int r2 = cb.red(); + int g1 = ca.green(); int g2 = cb.green(); + int b1 = ca.blue(); int b2 = cb.blue(); + int min = 0, max = 255; + + QRgb col; + + // Get minimum and maximum greylevel. + if (img.numColors()) { + // pseudocolor + for (int i = 0; i < img.numColors(); i++) { + col = img.color(i); + int mean = (qRed(col) + qGreen(col) + qBlue(col)) / 3; + min = QMIN(min, mean); + max = QMAX(max, mean); + } + } else { + // truecolor + for (int y=0; y < img.height(); y++) + for (int x=0; x < img.width(); x++) { + col = img.pixel(x, y); + int mean = (qRed(col) + qGreen(col) + qBlue(col)) / 3; + min = QMIN(min, mean); + max = QMAX(max, mean); + } + } + + // Conversion factors + float sr = ((float) r2 - r1) / (max - min); + float sg = ((float) g2 - g1) / (max - min); + float sb = ((float) b2 - b1) / (max - min); + + + // Repaint the image + if (img.numColors()) { + for (int i=0; i < img.numColors(); i++) { + col = img.color(i); + int mean = (qRed(col) + qGreen(col) + qBlue(col)) / 3; + int r = (int) (sr * (mean - min) + r1 + 0.5); + int g = (int) (sg * (mean - min) + g1 + 0.5); + int b = (int) (sb * (mean - min) + b1 + 0.5); + img.setColor(i, qRgba(r, g, b, qAlpha(col))); + } + } else { + for (int y=0; y < img.height(); y++) + for (int x=0; x < img.width(); x++) { + col = img.pixel(x, y); + int mean = (qRed(col) + qGreen(col) + qBlue(col)) / 3; + int r = (int) (sr * (mean - min) + r1 + 0.5); + int g = (int) (sg * (mean - min) + g1 + 0.5); + int b = (int) (sb * (mean - min) + b1 + 0.5); + img.setPixel(x, y, qRgba(r, g, b, qAlpha(col))); + } + } + + + // Dither if necessary + if ( (ncols <= 0) || ((img.numColors() != 0) && (img.numColors() <= ncols))) + return img; + + if (ncols == 1) ncols++; + if (ncols > 256) ncols = 256; + + QColor *pal = new QColor[ncols]; + sr = ((float) r2 - r1) / (ncols - 1); + sg = ((float) g2 - g1) / (ncols - 1); + sb = ((float) b2 - b1) / (ncols - 1); + + for (int i=0; i<ncols; i++) + pal[i] = QColor(r1 + int(sr*i), g1 + int(sg*i), b1 + int(sb*i)); + + dither(img, pal, ncols); + + delete[] pal; + return img; +} + + +//====================================================================== +// +// Fade effects +// +//====================================================================== + +QImage& KImageEffect::fade(QImage &img, float val, const QColor &color) +{ + if (img.width() == 0 || img.height() == 0) + return img; + + // We don't handle bitmaps + if (img.depth() == 1) + return img; + + unsigned char tbl[256]; + for (int i=0; i<256; i++) + tbl[i] = (int) (val * i + 0.5); + + int red = color.red(); + int green = color.green(); + int blue = color.blue(); + + QRgb col; + int r, g, b, cr, cg, cb; + + if (img.depth() <= 8) { + // pseudo color + for (int i=0; i<img.numColors(); i++) { + col = img.color(i); + cr = qRed(col); cg = qGreen(col); cb = qBlue(col); + if (cr > red) + r = cr - tbl[cr - red]; + else + r = cr + tbl[red - cr]; + if (cg > green) + g = cg - tbl[cg - green]; + else + g = cg + tbl[green - cg]; + if (cb > blue) + b = cb - tbl[cb - blue]; + else + b = cb + tbl[blue - cb]; + img.setColor(i, qRgba(r, g, b, qAlpha(col))); + } + + } else { + // truecolor + for (int y=0; y<img.height(); y++) { + QRgb *data = (QRgb *) img.scanLine(y); + for (int x=0; x<img.width(); x++) { + col = *data; + cr = qRed(col); cg = qGreen(col); cb = qBlue(col); + if (cr > red) + r = cr - tbl[cr - red]; + else + r = cr + tbl[red - cr]; + if (cg > green) + g = cg - tbl[cg - green]; + else + g = cg + tbl[green - cg]; + if (cb > blue) + b = cb - tbl[cb - blue]; + else + b = cb + tbl[blue - cb]; + *data++ = qRgba(r, g, b, qAlpha(col)); + } + } + } + + return img; +} + +//====================================================================== +// +// Color effects +// +//====================================================================== + +// This code is adapted from code (C) Rik Hemsley <rik@kde.org> +// +// The formula used (r + b + g) /3 is different from the qGray formula +// used by Qt. This is because our formula is much much faster. If, +// however, it turns out that this is producing sub-optimal images, +// then it will have to change (kurt) +// +// It does produce lower quality grayscale ;-) Use fast == true for the fast +// algorithm, false for the higher quality one (mosfet). +QImage& KImageEffect::toGray(QImage &img, bool fast) +{ + if (img.width() == 0 || img.height() == 0) + return img; + + if(fast){ + if (img.depth() == 32) { + register uchar * r(img.bits()); + register uchar * g(img.bits() + 1); + register uchar * b(img.bits() + 2); + + uchar * end(img.bits() + img.numBytes()); + + while (r != end) { + + *r = *g = *b = (((*r + *g) >> 1) + *b) >> 1; // (r + b + g) / 3 + + r += 4; + g += 4; + b += 4; + } + } + else + { + for (int i = 0; i < img.numColors(); i++) + { + register uint r = qRed(img.color(i)); + register uint g = qGreen(img.color(i)); + register uint b = qBlue(img.color(i)); + + register uint gray = (((r + g) >> 1) + b) >> 1; + img.setColor(i, qRgba(gray, gray, gray, qAlpha(img.color(i)))); + } + } + } + else{ + int pixels = img.depth() > 8 ? img.width()*img.height() : + img.numColors(); + unsigned int *data = img.depth() > 8 ? (unsigned int *)img.bits() : + (unsigned int *)img.colorTable(); + int val, i; + for(i=0; i < pixels; ++i){ + val = qGray(data[i]); + data[i] = qRgba(val, val, val, qAlpha(data[i])); + } + } + return img; +} + +// CT 29Jan2000 - desaturation algorithms +QImage& KImageEffect::desaturate(QImage &img, float desat) +{ + if (img.width() == 0 || img.height() == 0) + return img; + + if (desat < 0) desat = 0.; + if (desat > 1) desat = 1.; + int pixels = img.depth() > 8 ? img.width()*img.height() : + img.numColors(); + unsigned int *data = img.depth() > 8 ? (unsigned int *)img.bits() : + (unsigned int *)img.colorTable(); + int h, s, v, i; + QColor clr; // keep constructor out of loop (mosfet) + for(i=0; i < pixels; ++i){ + clr.setRgb(data[i]); + clr.hsv(&h, &s, &v); + clr.setHsv(h, (int)(s * (1. - desat)), v); + data[i] = clr.rgb(); + } + return img; +} + +// Contrast stuff (mosfet) +QImage& KImageEffect::contrast(QImage &img, int c) +{ + if (img.width() == 0 || img.height() == 0) + return img; + + if(c > 255) + c = 255; + if(c < -255) + c = -255; + int pixels = img.depth() > 8 ? img.width()*img.height() : + img.numColors(); + unsigned int *data = img.depth() > 8 ? (unsigned int *)img.bits() : + (unsigned int *)img.colorTable(); + int i, r, g, b; + for(i=0; i < pixels; ++i){ + r = qRed(data[i]); + g = qGreen(data[i]); + b = qBlue(data[i]); + if(qGray(data[i]) <= 127){ + if(r - c > 0) + r -= c; + else + r = 0; + if(g - c > 0) + g -= c; + else + g = 0; + if(b - c > 0) + b -= c; + else + b = 0; + } + else{ + if(r + c <= 255) + r += c; + else + r = 255; + if(g + c <= 255) + g += c; + else + g = 255; + if(b + c <= 255) + b += c; + else + b = 255; + } + data[i] = qRgba(r, g, b, qAlpha(data[i])); + } + return(img); +} + +//====================================================================== +// +// Dithering effects +// +//====================================================================== + +// adapted from kFSDither (C) 1997 Martin Jones (mjones@kde.org) +// +// Floyd-Steinberg dithering +// Ref: Bitmapped Graphics Programming in C++ +// Marv Luse, Addison-Wesley Publishing, 1993. +QImage& KImageEffect::dither(QImage &img, const QColor *palette, int size) +{ + if (img.width() == 0 || img.height() == 0 || + palette == 0 || img.depth() <= 8) + return img; + + QImage dImage( img.width(), img.height(), 8, size ); + int i; + + dImage.setNumColors( size ); + for ( i = 0; i < size; i++ ) + dImage.setColor( i, palette[ i ].rgb() ); + + int *rerr1 = new int [ img.width() * 2 ]; + int *gerr1 = new int [ img.width() * 2 ]; + int *berr1 = new int [ img.width() * 2 ]; + + memset( rerr1, 0, sizeof( int ) * img.width() * 2 ); + memset( gerr1, 0, sizeof( int ) * img.width() * 2 ); + memset( berr1, 0, sizeof( int ) * img.width() * 2 ); + + int *rerr2 = rerr1 + img.width(); + int *gerr2 = gerr1 + img.width(); + int *berr2 = berr1 + img.width(); + + for ( int j = 0; j < img.height(); j++ ) + { + uint *ip = (uint * )img.scanLine( j ); + uchar *dp = dImage.scanLine( j ); + + for ( i = 0; i < img.width(); i++ ) + { + rerr1[i] = rerr2[i] + qRed( *ip ); + rerr2[i] = 0; + gerr1[i] = gerr2[i] + qGreen( *ip ); + gerr2[i] = 0; + berr1[i] = berr2[i] + qBlue( *ip ); + berr2[i] = 0; + ip++; + } + + *dp++ = nearestColor( rerr1[0], gerr1[0], berr1[0], palette, size ); + + for ( i = 1; i < img.width()-1; i++ ) + { + int indx = nearestColor( rerr1[i], gerr1[i], berr1[i], palette, size ); + *dp = indx; + + int rerr = rerr1[i]; + rerr -= palette[indx].red(); + int gerr = gerr1[i]; + gerr -= palette[indx].green(); + int berr = berr1[i]; + berr -= palette[indx].blue(); + + // diffuse red error + rerr1[ i+1 ] += ( rerr * 7 ) >> 4; + rerr2[ i-1 ] += ( rerr * 3 ) >> 4; + rerr2[ i ] += ( rerr * 5 ) >> 4; + rerr2[ i+1 ] += ( rerr ) >> 4; + + // diffuse green error + gerr1[ i+1 ] += ( gerr * 7 ) >> 4; + gerr2[ i-1 ] += ( gerr * 3 ) >> 4; + gerr2[ i ] += ( gerr * 5 ) >> 4; + gerr2[ i+1 ] += ( gerr ) >> 4; + + // diffuse red error + berr1[ i+1 ] += ( berr * 7 ) >> 4; + berr2[ i-1 ] += ( berr * 3 ) >> 4; + berr2[ i ] += ( berr * 5 ) >> 4; + berr2[ i+1 ] += ( berr ) >> 4; + + dp++; + } + + *dp = nearestColor( rerr1[i], gerr1[i], berr1[i], palette, size ); + } + + delete [] rerr1; + delete [] gerr1; + delete [] berr1; + + img = dImage; + return img; +} + +int KImageEffect::nearestColor( int r, int g, int b, const QColor *palette, int size ) +{ + if (palette == 0) + return 0; + + int dr = palette[0].red() - r; + int dg = palette[0].green() - g; + int db = palette[0].blue() - b; + + int minDist = dr*dr + dg*dg + db*db; + int nearest = 0; + + for (int i = 1; i < size; i++ ) + { + dr = palette[i].red() - r; + dg = palette[i].green() - g; + db = palette[i].blue() - b; + + int dist = dr*dr + dg*dg + db*db; + + if ( dist < minDist ) + { + minDist = dist; + nearest = i; + } + } + + return nearest; +} + +bool KImageEffect::blend( + const QImage & upper, + const QImage & lower, + QImage & output +) +{ + if ( + upper.width() > lower.width() || + upper.height() > lower.height() || + upper.depth() != 32 || + lower.depth() != 32 + ) + { +#ifndef NDEBUG + std::cerr << "KImageEffect::blend : Sizes not correct\n" ; +#endif + return false; + } + + output = lower.copy(); + + register uchar *i, *o; + register int a; + register int col; + register int w = upper.width(); + int row(upper.height() - 1); + + do { + + i = upper.scanLine(row); + o = output.scanLine(row); + + col = w << 2; + --col; + + do { + + while (!(a = i[col]) && (col != 3)) { + --col; --col; --col; --col; + } + + --col; + o[col] += ((i[col] - o[col]) * a) >> 8; + + --col; + o[col] += ((i[col] - o[col]) * a) >> 8; + + --col; + o[col] += ((i[col] - o[col]) * a) >> 8; + + } while (col--); + + } while (row--); + + return true; +} + +#if 0 +// Not yet... +bool KImageEffect::blend( + const QImage & upper, + const QImage & lower, + QImage & output, + const QRect & destRect +) +{ + output = lower.copy(); + return output; +} + +#endif + +bool KImageEffect::blend( + int &x, int &y, + const QImage & upper, + const QImage & lower, + QImage & output +) +{ + int cx=0, cy=0, cw=upper.width(), ch=upper.height(); + + if ( upper.width() + x > lower.width() || + upper.height() + y > lower.height() || + x < 0 || y < 0 || + upper.depth() != 32 || lower.depth() != 32 ) + { + if ( x > lower.width() || y > lower.height() ) return false; + if ( upper.width()<=0 || upper.height() <= 0 ) return false; + if ( lower.width()<=0 || lower.height() <= 0 ) return false; + + if (x<0) {cx=-x; cw+=x; x=0; }; + if (cw + x > lower.width()) { cw=lower.width()-x; }; + if (y<0) {cy=-y; ch+=y; y=0; }; + if (ch + y > lower.height()) { ch=lower.height()-y; }; + + if ( cx >= upper.width() || cy >= upper.height() ) return true; + if ( cw <= 0 || ch <= 0 ) return true; + } + + output.create(cw,ch,32); +// output.setAlphaBuffer(true); // I should do some benchmarks to see if + // this is worth the effort + + register QRgb *i, *o, *b; + + register int a; + register int j,k; + for (j=0; j<ch; j++) + { + b=reinterpret_cast<QRgb *>(&lower.scanLine(y+j) [ (x+cw) << 2 ]); + i=reinterpret_cast<QRgb *>(&upper.scanLine(cy+j)[ (cx+cw) << 2 ]); + o=reinterpret_cast<QRgb *>(&output.scanLine(j) [ cw << 2 ]); + + k=cw-1; + --b; --i; --o; + do + { + while ( !(a=qAlpha(*i)) && k>0 ) + { + i--; +// *o=0; + *o=*b; + --o; --b; + k--; + }; +// *o=0xFF; + *o = qRgb(qRed(*b) + (((qRed(*i) - qRed(*b)) * a) >> 8), + qGreen(*b) + (((qGreen(*i) - qGreen(*b)) * a) >> 8), + qBlue(*b) + (((qBlue(*i) - qBlue(*b)) * a) >> 8)); + --i; --o; --b; + } while (k--); + } + + return true; +} + +bool KImageEffect::blendOnLower( + int x, int y, + const QImage & upper, + const QImage & lower +) +{ + int cx=0, cy=0, cw=upper.width(), ch=upper.height(); + + if ( upper.depth() != 32 || lower.depth() != 32 ) return false; + if ( x + cw > lower.width() || + y + ch > lower.height() || + x < 0 || y < 0 ) + { + if ( x > lower.width() || y > lower.height() ) return true; + if ( upper.width()<=0 || upper.height() <= 0 ) return true; + if ( lower.width()<=0 || lower.height() <= 0 ) return true; + + if (x<0) {cx=-x; cw+=x; x=0; }; + if (cw + x > lower.width()) { cw=lower.width()-x; }; + if (y<0) {cy=-y; ch+=y; y=0; }; + if (ch + y > lower.height()) { ch=lower.height()-y; }; + + if ( cx >= upper.width() || cy >= upper.height() ) return true; + if ( cw <= 0 || ch <= 0 ) return true; + } + + register uchar *i, *b; + register int a; + register int k; + + for (int j=0; j<ch; j++) + { + b=&lower.scanLine(y+j) [ (x+cw) << 2 ]; + i=&upper.scanLine(cy+j)[ (cx+cw) << 2 ]; + + k=cw-1; + --b; --i; + do + { +#ifndef WORDS_BIGENDIAN + while ( !(a=*i) && k>0 ) +#else + while ( !(a=*(i-3)) && k>0 ) +#endif + { + i-=4; b-=4; k--; + }; + +#ifndef WORDS_BIGENDIAN + --i; --b; + *b += ( ((*i - *b) * a) >> 8 ); + --i; --b; + *b += ( ((*i - *b) * a) >> 8 ); + --i; --b; + *b += ( ((*i - *b) * a) >> 8 ); + --i; --b; +#else + *b += ( ((*i - *b) * a) >> 8 ); + --i; --b; + *b += ( ((*i - *b) * a) >> 8 ); + --i; --b; + *b += ( ((*i - *b) * a) >> 8 ); + i -= 2; b -= 2; +#endif + } while (k--); + } + + return true; +} + +void KImageEffect::blendOnLower(const QImage &upper, const QPoint &upperOffset, + QImage &lower, const QRect &lowerRect) +{ + // clip rect + QRect lr = lowerRect & lower.rect(); + lr.setWidth( QMIN(lr.width(), upper.width()-upperOffset.x()) ); + lr.setHeight( QMIN(lr.height(), upper.height()-upperOffset.y()) ); + if ( !lr.isValid() ) return; + + // blend + for (int y = 0; y < lr.height(); y++) { + for (int x = 0; x < lr.width(); x++) { + QRgb *b = reinterpret_cast<QRgb*>(lower.scanLine(lr.y() + y)+ (lr.x() + x) * sizeof(QRgb)); + QRgb *d = reinterpret_cast<QRgb*>(upper.scanLine(upperOffset.y() + y) + (upperOffset.x() + x) * sizeof(QRgb)); + int a = qAlpha(*d); + *b = qRgb(qRed(*b) - (((qRed(*b) - qRed(*d)) * a) >> 8), + qGreen(*b) - (((qGreen(*b) - qGreen(*d)) * a) >> 8), + qBlue(*b) - (((qBlue(*b) - qBlue(*d)) * a) >> 8)); + } + } +} + +void KImageEffect::blendOnLower(const QImage &upper, const QPoint &upperOffset, + QImage &lower, const QRect &lowerRect, float opacity) +{ + // clip rect + QRect lr = lowerRect & lower.rect(); + lr.setWidth( QMIN(lr.width(), upper.width()-upperOffset.x()) ); + lr.setHeight( QMIN(lr.height(), upper.height()-upperOffset.y()) ); + if ( !lr.isValid() ) return; + + // blend + for (int y = 0; y < lr.height(); y++) { + for (int x = 0; x < lr.width(); x++) { + QRgb *b = reinterpret_cast<QRgb*>(lower.scanLine(lr.y() + y)+ (lr.x() + x) * sizeof(QRgb)); + QRgb *d = reinterpret_cast<QRgb*>(upper.scanLine(upperOffset.y() + y) + (upperOffset.x() + x) * sizeof(QRgb)); + int a = qRound(opacity * qAlpha(*d)); + *b = qRgb(qRed(*b) - (((qRed(*b) - qRed(*d)) * a) >> 8), + qGreen(*b) - (((qGreen(*b) - qGreen(*d)) * a) >> 8), + qBlue(*b) - (((qBlue(*b) - qBlue(*d)) * a) >> 8)); + } + } +} + +QRect KImageEffect::computeDestinationRect(const QSize &lowerSize, + Disposition disposition, QImage &upper) +{ + int w = lowerSize.width(); + int h = lowerSize.height(); + int ww = upper.width(); + int wh = upper.height(); + QRect d; + + switch (disposition) { + case NoImage: + break; + case Centered: + d.setRect((w - ww) / 2, (h - wh) / 2, ww, wh); + break; + case Tiled: + d.setRect(0, 0, w, h); + break; + case CenterTiled: + d.setCoords(-ww + ((w - ww) / 2) % ww, -wh + ((h - wh) / 2) % wh, + w-1, h-1); + break; + case Scaled: + upper = upper.smoothScale(w, h); + d.setRect(0, 0, w, h); + break; + case CenteredAutoFit: + if( ww <= w && wh <= h ) { + d.setRect((w - ww) / 2, (h - wh) / 2, ww, wh); // like Centered + break; + } + // fall through + case CenteredMaxpect: { + double sx = (double) w / ww; + double sy = (double) h / wh; + if (sx > sy) { + ww = (int)(sy * ww); + wh = h; + } else { + wh = (int)(sx * wh); + ww = w; + } + upper = upper.smoothScale(ww, wh); + d.setRect((w - ww) / 2, (h - wh) / 2, ww, wh); + break; + } + case TiledMaxpect: { + double sx = (double) w / ww; + double sy = (double) h / wh; + if (sx > sy) { + ww = (int)(sy * ww); + wh = h; + } else { + wh = (int)(sx * wh); + ww = w; + } + upper = upper.smoothScale(ww, wh); + d.setRect(0, 0, w, h); + break; + } + } + + return d; +} + +void KImageEffect::blendOnLower(QImage &upper, QImage &lower, + Disposition disposition, float opacity) +{ + QRect r = computeDestinationRect(lower.size(), disposition, upper); + for (int y = r.top(); y<r.bottom(); y += upper.height()) + for (int x = r.left(); x<r.right(); x += upper.width()) + blendOnLower(upper, QPoint(-QMIN(x, 0), -QMIN(y, 0)), + lower, QRect(x, y, upper.width(), upper.height()), opacity); +} + + +// For selected icons +QImage& KImageEffect::selectedImage( QImage &img, const QColor &col ) +{ + return blend( col, img, 0.5); +} + +// +// =================================================================== +// Effects originally ported from ImageMagick for PixiePlus, plus a few +// new ones. (mosfet 05/26/2003) +// =================================================================== +// +/* + Portions of this software are based on ImageMagick. Such portions are clearly +marked as being ported from ImageMagick. ImageMagick is copyrighted under the +following conditions: + +Copyright (C) 2003 ImageMagick Studio, a non-profit organization dedicated to +making software imaging solutions freely available. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files ("ImageMagick"), to deal +in ImageMagick without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of ImageMagick, and to permit persons to whom the ImageMagick is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of ImageMagick. + +The software is provided "as is", without warranty of any kind, express or +implied, including but not limited to the warranties of merchantability, +fitness for a particular purpose and noninfringement. In no event shall +ImageMagick Studio be liable for any claim, damages or other liability, +whether in an action of contract, tort or otherwise, arising from, out of or +in connection with ImageMagick or the use or other dealings in ImageMagick. + +Except as contained in this notice, the name of the ImageMagick Studio shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in ImageMagick without prior written authorization from the +ImageMagick Studio. +*/ + +QImage KImageEffect::sample(QImage &src, int w, int h) +{ + if(w == src.width() && h == src.height()) + return(src); + + int depth = src.depth(); + QImage dest(w, h, depth, depth <= 8 ? src.numColors() : 0, + depth == 1 ? QImage::LittleEndian : QImage::IgnoreEndian); + int *x_offset = (int *)malloc(w*sizeof(int)); + int *y_offset = (int *)malloc(h*sizeof(int)); + if(!x_offset || !y_offset){ +#ifndef NDEBUG + qWarning("KImageEffect::sample(): Unable to allocate pixel buffer"); +#endif + free(x_offset); + free(y_offset); + return(src); + } + + // init pixel offsets + for(int x=0; x < w; ++x) + x_offset[x] = (int)(x*src.width()/((double)w)); + for(int y=0; y < h; ++y) + y_offset[y] = (int)(y*src.height()/((double)h)); + + if(depth > 8){ // DirectClass source image + for(int y=0; y < h; ++y){ + unsigned int *destData = (unsigned int *)dest.scanLine(y); + unsigned int *srcData = (unsigned int *)src.scanLine(y_offset[y]); + for(int x=0; x < w; ++x) + destData[x] = srcData[x_offset[x]]; + } + } + else if(depth == 1) { + int r = src.bitOrder() == QImage::LittleEndian; + memcpy(dest.colorTable(), src.colorTable(), src.numColors()*sizeof(QRgb)); + for(int y=0; y < h; ++y){ + unsigned char *destData = dest.scanLine(y); + unsigned char *srcData = src.scanLine(y_offset[y]); + for(int x=0; x < w; ++x){ + int k = x_offset[x]; + int l = r ? (k & 7) : (7 - (k&7)); + if(srcData[k >> 3] & (1 << l)) + destData[x >> 3] |= 1 << (x & 7); + else + destData[x >> 3] &= ~(1 << (x & 7)); + } + } + } + else{ // PseudoClass source image + memcpy(dest.colorTable(), src.colorTable(), src.numColors()*sizeof(QRgb)); + for(int y=0; y < h; ++y){ + unsigned char *destData = dest.scanLine(y); + unsigned char *srcData = src.scanLine(y_offset[y]); + for(int x=0; x < w; ++x) + destData[x] = srcData[x_offset[x]]; + } + } + free(x_offset); + free(y_offset); + return(dest); +} + +void KImageEffect::threshold(QImage &img, unsigned int threshold) +{ + int i, count; + unsigned int *data; + if(img.depth() > 8){ // DirectClass + count = img.width()*img.height(); + data = (unsigned int *)img.bits(); + } + else{ // PsudeoClass + count = img.numColors(); + data = (unsigned int *)img.colorTable(); + } + for(i=0; i < count; ++i) + data[i] = intensityValue(data[i]) < threshold ? Qt::black.rgb() : Qt::white.rgb(); +} + +void KImageEffect::hull(const int x_offset, const int y_offset, + const int polarity, const int columns, + const int rows, + unsigned int *f, unsigned int *g) +{ + int x, y; + + unsigned int *p, *q, *r, *s; + unsigned int v; + if(f == NULL || g == NULL) + return; + p=f+(columns+2); + q=g+(columns+2); + r=p+(y_offset*(columns+2)+x_offset); + for (y=0; y < rows; y++){ + p++; + q++; + r++; + if(polarity > 0) + for (x=0; x < columns; x++){ + v=(*p); + if (*r > v) + v++; + *q=v; + p++; + q++; + r++; + } + else + for(x=0; x < columns; x++){ + v=(*p); + if (v > (unsigned int) (*r+1)) + v--; + *q=v; + p++; + q++; + r++; + } + p++; + q++; + r++; + } + p=f+(columns+2); + q=g+(columns+2); + r=q+(y_offset*(columns+2)+x_offset); + s=q-(y_offset*(columns+2)+x_offset); + for(y=0; y < rows; y++){ + p++; + q++; + r++; + s++; + if(polarity > 0) + for(x=0; x < (int) columns; x++){ + v=(*q); + if (((unsigned int) (*s+1) > v) && (*r > v)) + v++; + *p=v; + p++; + q++; + r++; + s++; + } + else + for (x=0; x < columns; x++){ + v=(*q); + if (((unsigned int) (*s+1) < v) && (*r < v)) + v--; + *p=v; + p++; + q++; + r++; + s++; + } + p++; + q++; + r++; + s++; + } +} + +QImage KImageEffect::despeckle(QImage &src) +{ + int i, j, x, y; + unsigned int *blue_channel, *red_channel, *green_channel, *buffer, + *alpha_channel; + int packets; + static const int + X[4]= {0, 1, 1,-1}, + Y[4]= {1, 0, 1, 1}; + + unsigned int *destData; + QImage dest(src.width(), src.height(), 32); + + packets = (src.width()+2)*(src.height()+2); + red_channel = (unsigned int *)calloc(packets, sizeof(unsigned int)); + green_channel = (unsigned int *)calloc(packets, sizeof(unsigned int)); + blue_channel = (unsigned int *)calloc(packets, sizeof(unsigned int)); + alpha_channel = (unsigned int *)calloc(packets, sizeof(unsigned int)); + buffer = (unsigned int *)calloc(packets, sizeof(unsigned int)); + if(!red_channel || ! green_channel || ! blue_channel || ! alpha_channel || + !buffer){ + free(red_channel); + free(green_channel); + free(blue_channel); + free(alpha_channel); + free(buffer); + return(src); + } + + // copy image pixels to color component buffers + j = src.width()+2; + if(src.depth() > 8){ // DirectClass source image + unsigned int *srcData; + for(y=0; y < src.height(); ++y){ + srcData = (unsigned int *)src.scanLine(y); + ++j; + for(x=0; x < src.width(); ++x){ + red_channel[j] = qRed(srcData[x]); + green_channel[j] = qGreen(srcData[x]); + blue_channel[j] = qBlue(srcData[x]); + alpha_channel[j] = qAlpha(srcData[x]); + ++j; + } + ++j; + } + } + else{ // PsudeoClass source image + unsigned char *srcData; + unsigned int *cTable = src.colorTable(); + unsigned int pixel; + for(y=0; y < src.height(); ++y){ + srcData = (unsigned char *)src.scanLine(y); + ++j; + for(x=0; x < src.width(); ++x){ + pixel = *(cTable+srcData[x]); + red_channel[j] = qRed(pixel); + green_channel[j] = qGreen(pixel); + blue_channel[j] = qBlue(pixel); + alpha_channel[j] = qAlpha(pixel); + ++j; + } + ++j; + } + } + // reduce speckle in red channel + for(i=0; i < 4; i++){ + hull(X[i],Y[i],1,src.width(),src.height(),red_channel,buffer); + hull(-X[i],-Y[i],1,src.width(),src.height(),red_channel,buffer); + hull(-X[i],-Y[i],-1,src.width(),src.height(),red_channel,buffer); + hull(X[i],Y[i],-1,src.width(),src.height(),red_channel,buffer); + } + // reduce speckle in green channel + for (i=0; i < packets; i++) + buffer[i]=0; + for (i=0; i < 4; i++){ + hull(X[i],Y[i],1,src.width(),src.height(),green_channel,buffer); + hull(-X[i],-Y[i],1,src.width(),src.height(),green_channel,buffer); + hull(-X[i],-Y[i],-1,src.width(),src.height(),green_channel,buffer); + hull(X[i],Y[i],-1,src.width(),src.height(),green_channel,buffer); + } + // reduce speckle in blue channel + for (i=0; i < packets; i++) + buffer[i]=0; + for (i=0; i < 4; i++){ + hull(X[i],Y[i],1,src.width(),src.height(),blue_channel,buffer); + hull(-X[i],-Y[i],1,src.width(),src.height(),blue_channel,buffer); + hull(-X[i],-Y[i],-1,src.width(),src.height(),blue_channel,buffer); + hull(X[i],Y[i],-1,src.width(),src.height(),blue_channel,buffer); + } + // copy color component buffers to despeckled image + j = dest.width()+2; + for(y=0; y < dest.height(); ++y) + { + destData = (unsigned int *)dest.scanLine(y); + ++j; + for (x=0; x < dest.width(); ++x) + { + destData[x] = qRgba(red_channel[j], green_channel[j], + blue_channel[j], alpha_channel[j]); + ++j; + } + ++j; + } + free(buffer); + free(red_channel); + free(green_channel); + free(blue_channel); + free(alpha_channel); + return(dest); +} + +unsigned int KImageEffect::generateNoise(unsigned int pixel, + NoiseType noise_type) +{ +#define NoiseEpsilon 1.0e-5 +#define NoiseMask 0x7fff +#define SigmaUniform 4.0 +#define SigmaGaussian 4.0 +#define SigmaImpulse 0.10 +#define SigmaLaplacian 10.0 +#define SigmaMultiplicativeGaussian 0.5 +#define SigmaPoisson 0.05 +#define TauGaussian 20.0 + + double alpha, beta, sigma, value; + alpha=(double) (rand() & NoiseMask)/NoiseMask; + if (alpha == 0.0) + alpha=1.0; + switch(noise_type){ + case UniformNoise: + default: + { + value=(double) pixel+SigmaUniform*(alpha-0.5); + break; + } + case GaussianNoise: + { + double tau; + + beta=(double) (rand() & NoiseMask)/NoiseMask; + sigma=sqrt(-2.0*log(alpha))*cos(2.0*M_PI*beta); + tau=sqrt(-2.0*log(alpha))*sin(2.0*M_PI*beta); + value=(double) pixel+ + (sqrt((double) pixel)*SigmaGaussian*sigma)+(TauGaussian*tau); + break; + } + case MultiplicativeGaussianNoise: + { + if (alpha <= NoiseEpsilon) + sigma=MaxRGB; + else + sigma=sqrt(-2.0*log(alpha)); + beta=(rand() & NoiseMask)/NoiseMask; + value=(double) pixel+ + pixel*SigmaMultiplicativeGaussian*sigma*cos(2.0*M_PI*beta); + break; + } + case ImpulseNoise: + { + if (alpha < (SigmaImpulse/2.0)) + value=0; + else + if (alpha >= (1.0-(SigmaImpulse/2.0))) + value=MaxRGB; + else + value=pixel; + break; + } + case LaplacianNoise: + { + if (alpha <= 0.5) + { + if (alpha <= NoiseEpsilon) + value=(double) pixel-MaxRGB; + else + value=(double) pixel+SigmaLaplacian*log(2.0*alpha); + break; + } + beta=1.0-alpha; + if (beta <= (0.5*NoiseEpsilon)) + value=(double) pixel+MaxRGB; + else + value=(double) pixel-SigmaLaplacian*log(2.0*beta); + break; + } + case PoissonNoise: + { + register int + i; + + for (i=0; alpha > exp(-SigmaPoisson*pixel); i++) + { + beta=(double) (rand() & NoiseMask)/NoiseMask; + alpha=alpha*beta; + } + value=i/SigmaPoisson; + break; + } + } + if(value < 0.0) + return(0); + if(value > MaxRGB) + return(MaxRGB); + return((unsigned int) (value+0.5)); +} + +QImage KImageEffect::addNoise(QImage &src, NoiseType noise_type) +{ + int x, y; + QImage dest(src.width(), src.height(), 32); + unsigned int *destData; + + if(src.depth() > 8){ // DirectClass source image + unsigned int *srcData; + for(y=0; y < src.height(); ++y){ + srcData = (unsigned int *)src.scanLine(y); + destData = (unsigned int *)dest.scanLine(y); + for(x=0; x < src.width(); ++x){ + destData[x] = qRgba(generateNoise(qRed(srcData[x]), noise_type), + generateNoise(qGreen(srcData[x]), noise_type), + generateNoise(qBlue(srcData[x]), noise_type), + qAlpha(srcData[x])); + } + } + } + else{ // PsudeoClass source image + unsigned char *srcData; + unsigned int *cTable = src.colorTable(); + unsigned int pixel; + for(y=0; y < src.height(); ++y){ + srcData = (unsigned char *)src.scanLine(y); + destData = (unsigned int *)dest.scanLine(y); + for(x=0; x < src.width(); ++x){ + pixel = *(cTable+srcData[x]); + destData[x] = qRgba(generateNoise(qRed(pixel), noise_type), + generateNoise(qGreen(pixel), noise_type), + generateNoise(qBlue(pixel), noise_type), + qAlpha(pixel)); + } + } + + } + return(dest); +} + +unsigned int KImageEffect::interpolateColor(QImage *image, double x_offset, + double y_offset, + unsigned int background) +{ + double alpha, beta; + unsigned int p, q, r, s; + int x, y; + + x = (int)x_offset; + y = (int)y_offset; + if((x < -1) || (x >= image->width()) || (y < -1) || (y >= image->height())) + return(background); + if(image->depth() > 8){ + if((x >= 0) && (y >= 0) && (x < (image->width()-1)) && (y < (image->height()-1))) { + unsigned int *t = (unsigned int *)image->scanLine(y); + p = t[x]; + q = t[x+1]; + r = t[x+image->width()]; + s = t[x+image->width()+1]; + } + else{ + unsigned int *t = (unsigned int *)image->scanLine(y); + p = background; + if((x >= 0) && (y >= 0)){ + p = t[x]; + } + q = background; + if(((x+1) < image->width()) && (y >= 0)){ + q = t[x+1]; + } + r = background; + if((x >= 0) && ((y+1) < image->height())){ + t = (unsigned int *)image->scanLine(y+1); + r = t[x+image->width()]; + } + s = background; + if(((x+1) < image->width()) && ((y+1) < image->height())){ + t = (unsigned int *)image->scanLine(y+1); + s = t[x+image->width()+1]; + } + + } + } + else{ + unsigned int *colorTable = (unsigned int *)image->colorTable(); + if((x >= 0) && (y >= 0) && (x < (image->width()-1)) && (y < (image->height()-1))) { + unsigned char *t; + t = (unsigned char *)image->scanLine(y); + p = *(colorTable+t[x]); + q = *(colorTable+t[x+1]); + t = (unsigned char *)image->scanLine(y+1); + r = *(colorTable+t[x]); + s = *(colorTable+t[x+1]); + } + else{ + unsigned char *t; + p = background; + if((x >= 0) && (y >= 0)){ + t = (unsigned char *)image->scanLine(y); + p = *(colorTable+t[x]); + } + q = background; + if(((x+1) < image->width()) && (y >= 0)){ + t = (unsigned char *)image->scanLine(y); + q = *(colorTable+t[x+1]); + } + r = background; + if((x >= 0) && ((y+1) < image->height())){ + t = (unsigned char *)image->scanLine(y+1); + r = *(colorTable+t[x]); + } + s = background; + if(((x+1) < image->width()) && ((y+1) < image->height())){ + t = (unsigned char *)image->scanLine(y+1); + s = *(colorTable+t[x+1]); + } + + } + + } + x_offset -= floor(x_offset); + y_offset -= floor(y_offset); + alpha = 1.0-x_offset; + beta = 1.0-y_offset; + + return(qRgba((unsigned char)(beta*(alpha*qRed(p)+x_offset*qRed(q))+y_offset*(alpha*qRed(r)+x_offset*qRed(s))), + (unsigned char)(beta*(alpha*qGreen(p)+x_offset*qGreen(q))+y_offset*(alpha*qGreen(r)+x_offset*qGreen(s))), + (unsigned char)(beta*(alpha*qBlue(p)+x_offset*qBlue(q))+y_offset*(alpha*qBlue(r)+x_offset*qBlue(s))), + (unsigned char)(beta*(alpha*qAlpha(p)+x_offset*qAlpha(q))+y_offset*(alpha*qAlpha(r)+x_offset*qAlpha(s))))); +} + +QImage KImageEffect::implode(QImage &src, double factor, + unsigned int background) +{ + double amount, distance, radius; + double x_center, x_distance, x_scale; + double y_center, y_distance, y_scale; + unsigned int *destData; + int x, y; + + QImage dest(src.width(), src.height(), 32); + + // compute scaling factor + x_scale = 1.0; + y_scale = 1.0; + x_center = (double)0.5*src.width(); + y_center = (double)0.5*src.height(); + radius=x_center; + if(src.width() > src.height()) + y_scale = (double)src.width()/src.height(); + else if(src.width() < src.height()){ + x_scale = (double) src.height()/src.width(); + radius = y_center; + } + amount=factor/10.0; + if(amount >= 0) + amount/=10.0; + if(src.depth() > 8){ // DirectClass source image + unsigned int *srcData; + for(y=0; y < src.height(); ++y){ + srcData = (unsigned int *)src.scanLine(y); + destData = (unsigned int *)dest.scanLine(y); + y_distance=y_scale*(y-y_center); + for(x=0; x < src.width(); ++x){ + destData[x] = srcData[x]; + x_distance = x_scale*(x-x_center); + distance= x_distance*x_distance+y_distance*y_distance; + if(distance < (radius*radius)){ + double factor; + // Implode the pixel. + factor=1.0; + if(distance > 0.0) + factor= + pow(sin(0.5000000000000001*M_PI*sqrt(distance)/radius),-amount); + destData[x] = interpolateColor(&src, factor*x_distance/x_scale+x_center, + factor*y_distance/y_scale+y_center, + background); + } + } + } + } + else{ // PsudeoClass source image + unsigned char *srcData; + unsigned char idx; + unsigned int *cTable = src.colorTable(); + for(y=0; y < src.height(); ++y){ + srcData = (unsigned char *)src.scanLine(y); + destData = (unsigned int *)dest.scanLine(y); + y_distance=y_scale*(y-y_center); + for(x=0; x < src.width(); ++x){ + idx = srcData[x]; + destData[x] = cTable[idx]; + x_distance = x_scale*(x-x_center); + distance= x_distance*x_distance+y_distance*y_distance; + if(distance < (radius*radius)){ + double factor; + // Implode the pixel. + factor=1.0; + if(distance > 0.0) + factor= + pow(sin(0.5000000000000001*M_PI*sqrt(distance)/radius),-amount); + destData[x] = interpolateColor(&src, factor*x_distance/x_scale+x_center, + factor*y_distance/y_scale+y_center, + background); + } + } + } + + } + return(dest); +} + +QImage KImageEffect::rotate(QImage &img, RotateDirection r) +{ + QImage dest; + int x, y; + if(img.depth() > 8){ + unsigned int *srcData, *destData; + switch(r){ + case Rotate90: + dest.create(img.height(), img.width(), img.depth()); + for(y=0; y < img.height(); ++y){ + srcData = (unsigned int *)img.scanLine(y); + for(x=0; x < img.width(); ++x){ + destData = (unsigned int *)dest.scanLine(x); + destData[img.height()-y-1] = srcData[x]; + } + } + break; + case Rotate180: + dest.create(img.width(), img.height(), img.depth()); + for(y=0; y < img.height(); ++y){ + srcData = (unsigned int *)img.scanLine(y); + destData = (unsigned int *)dest.scanLine(img.height()-y-1); + for(x=0; x < img.width(); ++x) + destData[img.width()-x-1] = srcData[x]; + } + break; + case Rotate270: + dest.create(img.height(), img.width(), img.depth()); + for(y=0; y < img.height(); ++y){ + srcData = (unsigned int *)img.scanLine(y); + for(x=0; x < img.width(); ++x){ + destData = (unsigned int *)dest.scanLine(img.width()-x-1); + destData[y] = srcData[x]; + } + } + break; + default: + dest = img; + break; + } + } + else{ + unsigned char *srcData, *destData; + unsigned int *srcTable, *destTable; + switch(r){ + case Rotate90: + dest.create(img.height(), img.width(), img.depth()); + dest.setNumColors(img.numColors()); + srcTable = (unsigned int *)img.colorTable(); + destTable = (unsigned int *)dest.colorTable(); + for(x=0; x < img.numColors(); ++x) + destTable[x] = srcTable[x]; + for(y=0; y < img.height(); ++y){ + srcData = (unsigned char *)img.scanLine(y); + for(x=0; x < img.width(); ++x){ + destData = (unsigned char *)dest.scanLine(x); + destData[img.height()-y-1] = srcData[x]; + } + } + break; + case Rotate180: + dest.create(img.width(), img.height(), img.depth()); + dest.setNumColors(img.numColors()); + srcTable = (unsigned int *)img.colorTable(); + destTable = (unsigned int *)dest.colorTable(); + for(x=0; x < img.numColors(); ++x) + destTable[x] = srcTable[x]; + for(y=0; y < img.height(); ++y){ + srcData = (unsigned char *)img.scanLine(y); + destData = (unsigned char *)dest.scanLine(img.height()-y-1); + for(x=0; x < img.width(); ++x) + destData[img.width()-x-1] = srcData[x]; + } + break; + case Rotate270: + dest.create(img.height(), img.width(), img.depth()); + dest.setNumColors(img.numColors()); + srcTable = (unsigned int *)img.colorTable(); + destTable = (unsigned int *)dest.colorTable(); + for(x=0; x < img.numColors(); ++x) + destTable[x] = srcTable[x]; + for(y=0; y < img.height(); ++y){ + srcData = (unsigned char *)img.scanLine(y); + for(x=0; x < img.width(); ++x){ + destData = (unsigned char *)dest.scanLine(img.width()-x-1); + destData[y] = srcData[x]; + } + } + break; + default: + dest = img; + break; + } + + } + return(dest); +} + +void KImageEffect::solarize(QImage &img, double factor) +{ + int i, count; + int threshold; + unsigned int *data; + + threshold = (int)(factor*(MaxRGB+1)/100.0); + if(img.depth() < 32){ + data = (unsigned int *)img.colorTable(); + count = img.numColors(); + } + else{ + data = (unsigned int *)img.bits(); + count = img.width()*img.height(); + } + for(i=0; i < count; ++i){ + data[i] = qRgba(qRed(data[i]) > threshold ? MaxRGB-qRed(data[i]) : qRed(data[i]), + qGreen(data[i]) > threshold ? MaxRGB-qGreen(data[i]) : qGreen(data[i]), + qBlue(data[i]) > threshold ? MaxRGB-qBlue(data[i]) : qBlue(data[i]), + qAlpha(data[i])); + } +} + +QImage KImageEffect::spread(QImage &src, unsigned int amount) +{ + int quantum, x, y; + int x_distance, y_distance; + if(src.width() < 3 || src.height() < 3) + return(src); + QImage dest(src); + dest.detach(); + quantum=(amount+1) >> 1; + if(src.depth() > 8){ // DirectClass source image + unsigned int *p, *q; + for(y=0; y < src.height(); y++){ + q = (unsigned int *)dest.scanLine(y); + for(x=0; x < src.width(); x++){ + x_distance = x + ((rand() & (amount+1))-quantum); + y_distance = y + ((rand() & (amount+1))-quantum); + x_distance = QMIN(x_distance, src.width()-1); + y_distance = QMIN(y_distance, src.height()-1); + if(x_distance < 0) + x_distance = 0; + if(y_distance < 0) + y_distance = 0; + p = (unsigned int *)src.scanLine(y_distance); + p += x_distance; + *q++=(*p); + } + } + } + else{ // PsudeoClass source image + // just do colortable values + unsigned char *p, *q; + for(y=0; y < src.height(); y++){ + q = (unsigned char *)dest.scanLine(y); + for(x=0; x < src.width(); x++){ + x_distance = x + ((rand() & (amount+1))-quantum); + y_distance = y + ((rand() & (amount+1))-quantum); + x_distance = QMIN(x_distance, src.width()-1); + y_distance = QMIN(y_distance, src.height()-1); + if(x_distance < 0) + x_distance = 0; + if(y_distance < 0) + y_distance = 0; + p = (unsigned char *)src.scanLine(y_distance); + p += x_distance; + *q++=(*p); + } + } + } + return(dest); +} + +QImage KImageEffect::swirl(QImage &src, double degrees, + unsigned int background) +{ + double cosine, distance, factor, radius, sine, x_center, x_distance, + x_scale, y_center, y_distance, y_scale; + int x, y; + unsigned int *q; + QImage dest(src.width(), src.height(), 32); + + // compute scaling factor + x_center = src.width()/2.0; + y_center = src.height()/2.0; + radius = QMAX(x_center,y_center); + x_scale=1.0; + y_scale=1.0; + if(src.width() > src.height()) + y_scale=(double)src.width()/src.height(); + else if(src.width() < src.height()) + x_scale=(double)src.height()/src.width(); + degrees=DegreesToRadians(degrees); + // swirl each row + if(src.depth() > 8){ // DirectClass source image + unsigned int *p; + for(y=0; y < src.height(); y++){ + p = (unsigned int *)src.scanLine(y); + q = (unsigned int *)dest.scanLine(y); + y_distance = y_scale*(y-y_center); + for(x=0; x < src.width(); x++){ + // determine if the pixel is within an ellipse + *q=(*p); + x_distance = x_scale*(x-x_center); + distance = x_distance*x_distance+y_distance*y_distance; + if (distance < (radius*radius)){ + // swirl + factor = 1.0-sqrt(distance)/radius; + sine = sin(degrees*factor*factor); + cosine = cos(degrees*factor*factor); + *q = interpolateColor(&src, + (cosine*x_distance-sine*y_distance)/x_scale+x_center, + (sine*x_distance+cosine*y_distance)/y_scale+y_center, + background); + } + p++; + q++; + } + } + } + else{ // PsudeoClass source image + unsigned char *p; + unsigned int *cTable = (unsigned int *)src.colorTable(); + for(y=0; y < src.height(); y++){ + p = (unsigned char *)src.scanLine(y); + q = (unsigned int *)dest.scanLine(y); + y_distance = y_scale*(y-y_center); + for(x=0; x < src.width(); x++){ + // determine if the pixel is within an ellipse + *q = *(cTable+(*p)); + x_distance = x_scale*(x-x_center); + distance = x_distance*x_distance+y_distance*y_distance; + if (distance < (radius*radius)){ + // swirl + factor = 1.0-sqrt(distance)/radius; + sine = sin(degrees*factor*factor); + cosine = cos(degrees*factor*factor); + *q = interpolateColor(&src, + (cosine*x_distance-sine*y_distance)/x_scale+x_center, + (sine*x_distance+cosine*y_distance)/y_scale+y_center, + background); + } + p++; + q++; + } + } + + } + return(dest); +} + +QImage KImageEffect::wave(QImage &src, double amplitude, double wavelength, + unsigned int background) +{ + double *sine_map; + int x, y; + unsigned int *q; + + QImage dest(src.width(), src.height() + (int)(2*fabs(amplitude)), 32); + // allocate sine map + sine_map = (double *)malloc(dest.width()*sizeof(double)); + if(!sine_map) + return(src); + for(x=0; x < dest.width(); ++x) + sine_map[x]=fabs(amplitude)+amplitude*sin((2*M_PI*x)/wavelength); + // wave image + for(y=0; y < dest.height(); ++y){ + q = (unsigned int *)dest.scanLine(y); + for (x=0; x < dest.width(); x++){ + *q=interpolateColor(&src, x, (int)(y-sine_map[x]), background); + ++q; + } + } + free(sine_map); + return(dest); +} + +// +// The following methods work by computing a value from neighboring pixels +// (mosfet 05/26/03) +// + +// New algorithms based on ImageMagick 5.5.6 (05/26/03) + +QImage KImageEffect::oilPaint(QImage &src, int /*radius*/) +{ + /* binary compat method - remove me when possible! */ + return(oilPaintConvolve(src, 0)); +} + +QImage KImageEffect::oilPaintConvolve(QImage &src, double radius) +{ + unsigned long count /*,*histogram*/; + unsigned long histogram[256]; + unsigned int k; + int width; + int x, y, mx, my, sx, sy; + int mcx, mcy; + unsigned int *s=0, *q; + + if(src.depth() < 32) + src.convertDepth(32); + QImage dest(src); + dest.detach(); + + width = getOptimalKernelWidth(radius, 0.5); + if(src.width() < width){ + qWarning("KImageEffect::oilPaintConvolve(): Image is smaller than radius!"); + return(dest); + } + /* + histogram = (unsigned long *)malloc(256*sizeof(unsigned long)); + if(!histogram){ + qWarning("KImageEffect::oilPaintColvolve(): Unable to allocate memory!"); + return(dest); + } + */ + unsigned int **jumpTable = (unsigned int **)src.jumpTable(); + for(y=0; y < dest.height(); ++y){ + sy = y-(width/2); + q = (unsigned int *)dest.scanLine(y); + for(x=0; x < dest.width(); ++x){ + count = 0; + memset(histogram, 0, 256*sizeof(unsigned long)); + //memset(histogram, 0, 256); + sy = y-(width/2); + for(mcy=0; mcy < width; ++mcy, ++sy){ + my = sy < 0 ? 0 : sy > src.height()-1 ? + src.height()-1 : sy; + sx = x+(-width/2); + for(mcx=0; mcx < width; ++mcx, ++sx){ + mx = sx < 0 ? 0 : sx > src.width()-1 ? + src.width()-1 : sx; + + k = intensityValue(jumpTable[my][mx]); + if(k > 255){ + qWarning("KImageEffect::oilPaintConvolve(): k is %d", + k); + k = 255; + } + histogram[k]++; + if(histogram[k] > count){ + count = histogram[k]; + s = jumpTable[my]+mx; + } + } + } + if (s) + *q++ = (*s); + } + } + /* liberateMemory((histogram); */ + return(dest); +} + +QImage KImageEffect::charcoal(QImage &src, double /*factor*/) +{ + /* binary compat method - remove me when possible! */ + return(charcoal(src, 0, 1)); +} + +QImage KImageEffect::charcoal(QImage &src, double radius, double sigma) +{ + QImage img(edge(src, radius)); + img = blur(img, radius, sigma); + normalize(img); + img.invertPixels(false); + KImageEffect::toGray(img); + return(img); +} + +void KImageEffect::normalize(QImage &image) +{ + struct double_packet high, low, intensity, *histogram; + struct short_packet *normalize_map; + Q_INT64 number_pixels; + int x, y; + unsigned int *p, *q; + register long i; + unsigned long threshold_intensity; + unsigned char r, g, b, a; + + if(image.depth() < 32) // result will always be 32bpp + image = image.convertDepth(32); + + histogram = (struct double_packet *) + malloc(256*sizeof(struct double_packet)); + normalize_map = (struct short_packet *) + malloc(256*sizeof(struct short_packet)); + + if(!histogram || !normalize_map){ + if(histogram) + liberateMemory(&histogram); + if(normalize_map) + liberateMemory(&normalize_map); + qWarning("KImageEffect::normalize(): Unable to allocate memory!"); + return; + } + + /* + Form histogram. + */ + memset(histogram, 0, 256*sizeof(struct double_packet)); + for(y=0; y < image.height(); ++y){ + p = (unsigned int *)image.scanLine(y); + for(x=0; x < image.width(); ++x){ + histogram[(unsigned char)(qRed(*p))].red++; + histogram[(unsigned char)(qGreen(*p))].green++; + histogram[(unsigned char)(qBlue(*p))].blue++; + histogram[(unsigned char)(qAlpha(*p))].alpha++; + p++; + } + } + + /* + Find the histogram boundaries by locating the 0.1 percent levels. + */ + number_pixels = (Q_INT64)image.width()*image.height(); + threshold_intensity = number_pixels/1000; + + /* red */ + memset(&intensity, 0, sizeof(struct double_packet)); + memset(&high, 0, sizeof(struct double_packet)); + memset(&low, 0, sizeof(struct double_packet)); + for(high.red=255; high.red != 0; high.red--){ + intensity.red+=histogram[(unsigned char)high.red].red; + if(intensity.red > threshold_intensity) + break; + } + if(low.red == high.red){ + threshold_intensity = 0; + memset(&intensity, 0, sizeof(struct double_packet)); + for(low.red=0; low.red < 255; low.red++){ + intensity.red+=histogram[(unsigned char)low.red].red; + if(intensity.red > threshold_intensity) + break; + } + memset(&intensity, 0, sizeof(struct double_packet)); + for(high.red=255; high.red != 0; high.red--){ + intensity.red+=histogram[(unsigned char)high.red].red; + if(intensity.red > threshold_intensity) + break; + } + } + + /* green */ + memset(&intensity, 0, sizeof(struct double_packet)); + for(high.green=255; high.green != 0; high.green--){ + intensity.green+=histogram[(unsigned char)high.green].green; + if(intensity.green > threshold_intensity) + break; + } + if(low.green == high.green){ + threshold_intensity = 0; + memset(&intensity, 0, sizeof(struct double_packet)); + for(low.green=0; low.green < 255; low.green++){ + intensity.green+=histogram[(unsigned char)low.green].green; + if(intensity.green > threshold_intensity) + break; + } + memset(&intensity,0,sizeof(struct double_packet)); + for(high.green=255; high.green != 0; high.green--){ + intensity.green+=histogram[(unsigned char)high.green].green; + if(intensity.green > threshold_intensity) + break; + } + } + + /* blue */ + memset(&intensity, 0, sizeof(struct double_packet)); + for(high.blue=255; high.blue != 0; high.blue--){ + intensity.blue+=histogram[(unsigned char)high.blue].blue; + if(intensity.blue > threshold_intensity) + break; + } + if(low.blue == high.blue){ + threshold_intensity = 0; + memset(&intensity, 0, sizeof(struct double_packet)); + for(low.blue=0; low.blue < 255; low.blue++){ + intensity.blue+=histogram[(unsigned char)low.blue].blue; + if(intensity.blue > threshold_intensity) + break; + } + memset(&intensity,0,sizeof(struct double_packet)); + for(high.blue=255; high.blue != 0; high.blue--){ + intensity.blue+=histogram[(unsigned char)high.blue].blue; + if(intensity.blue > threshold_intensity) + break; + } + } + + /* alpha */ + memset(&intensity, 0, sizeof(struct double_packet)); + for(high.alpha=255; high.alpha != 0; high.alpha--){ + intensity.alpha+=histogram[(unsigned char)high.alpha].alpha; + if(intensity.alpha > threshold_intensity) + break; + } + if(low.alpha == high.alpha){ + threshold_intensity = 0; + memset(&intensity, 0, sizeof(struct double_packet)); + for(low.alpha=0; low.alpha < 255; low.alpha++){ + intensity.alpha+=histogram[(unsigned char)low.alpha].alpha; + if(intensity.alpha > threshold_intensity) + break; + } + memset(&intensity,0,sizeof(struct double_packet)); + for(high.alpha=255; high.alpha != 0; high.alpha--){ + intensity.alpha+=histogram[(unsigned char)high.alpha].alpha; + if(intensity.alpha > threshold_intensity) + break; + } + } + liberateMemory(&histogram); + + /* + Stretch the histogram to create the normalized image mapping. + */ + + // should the maxes be 65535? + memset(normalize_map, 0 ,256*sizeof(struct short_packet)); + for(i=0; i <= (long) 255; i++){ + if(i < (long) low.red) + normalize_map[i].red=0; + else if (i > (long) high.red) + normalize_map[i].red=65535; + else if (low.red != high.red) + normalize_map[i].red = + (unsigned short)((65535*(i-low.red))/(high.red-low.red)); + + if(i < (long) low.green) + normalize_map[i].green=0; + else if (i > (long) high.green) + normalize_map[i].green=65535; + else if (low.green != high.green) + normalize_map[i].green = + (unsigned short)((65535*(i-low.green))/(high.green-low.green)); + + if(i < (long) low.blue) + normalize_map[i].blue=0; + else if (i > (long) high.blue) + normalize_map[i].blue=65535; + else if (low.blue != high.blue) + normalize_map[i].blue = + (unsigned short)((65535*(i-low.blue))/(high.blue-low.blue)); + + if(i < (long) low.alpha) + normalize_map[i].alpha=0; + else if (i > (long) high.alpha) + normalize_map[i].alpha=65535; + else if (low.alpha != high.alpha) + normalize_map[i].alpha = + (unsigned short)((65535*(i-low.alpha))/(high.alpha-low.alpha)); + + } + + for(y=0; y < image.height(); ++y){ + q = (unsigned int *)image.scanLine(y); + for(x=0; x < image.width(); ++x){ + if(low.red != high.red) + r = (normalize_map[(unsigned short)(qRed(q[x]))].red)/257; + else + r = qRed(q[x]); + if(low.green != high.green) + g = (normalize_map[(unsigned short)(qGreen(q[x]))].green)/257; + else + g = qGreen(q[x]); + if(low.blue != high.blue) + b = (normalize_map[(unsigned short)(qBlue(q[x]))].blue)/257; + else + b = qBlue(q[x]); + if(low.alpha != high.alpha) + a = (normalize_map[(unsigned short)(qAlpha(q[x]))].alpha)/257; + else + a = qAlpha(q[x]); + q[x] = qRgba(r, g, b, a); + } + } + liberateMemory(&normalize_map); +} + +void KImageEffect::equalize(QImage &image) +{ + struct double_packet high, low, intensity, *map, *histogram; + struct short_packet *equalize_map; + int x, y; + unsigned int *p, *q; + long i; + unsigned char r, g, b, a; + + if(image.depth() < 32) // result will always be 32bpp + image = image.convertDepth(32); + + histogram=(struct double_packet *) malloc(256*sizeof(struct double_packet)); + map=(struct double_packet *) malloc(256*sizeof(struct double_packet)); + equalize_map=(struct short_packet *)malloc(256*sizeof(struct short_packet)); + if(!histogram || !map || !equalize_map){ + if(histogram) + liberateMemory(&histogram); + if(map) + liberateMemory(&map); + if(equalize_map) + liberateMemory(&equalize_map); + qWarning("KImageEffect::equalize(): Unable to allocate memory!"); + return; + } + + /* + Form histogram. + */ + memset(histogram, 0, 256*sizeof(struct double_packet)); + for(y=0; y < image.height(); ++y){ + p = (unsigned int *)image.scanLine(y); + for(x=0; x < image.width(); ++x){ + histogram[(unsigned char)(qRed(*p))].red++; + histogram[(unsigned char)(qGreen(*p))].green++; + histogram[(unsigned char)(qBlue(*p))].blue++; + histogram[(unsigned char)(qAlpha(*p))].alpha++; + p++; + } + } + /* + Integrate the histogram to get the equalization map. + */ + memset(&intensity, 0 ,sizeof(struct double_packet)); + for(i=0; i <= 255; ++i){ + intensity.red += histogram[i].red; + intensity.green += histogram[i].green; + intensity.blue += histogram[i].blue; + intensity.alpha += histogram[i].alpha; + map[i]=intensity; + } + low=map[0]; + high=map[255]; + memset(equalize_map, 0, 256*sizeof(short_packet)); + for(i=0; i <= 255; ++i){ + if(high.red != low.red) + equalize_map[i].red=(unsigned short) + ((65535*(map[i].red-low.red))/(high.red-low.red)); + if(high.green != low.green) + equalize_map[i].green=(unsigned short) + ((65535*(map[i].green-low.green))/(high.green-low.green)); + if(high.blue != low.blue) + equalize_map[i].blue=(unsigned short) + ((65535*(map[i].blue-low.blue))/(high.blue-low.blue)); + if(high.alpha != low.alpha) + equalize_map[i].alpha=(unsigned short) + ((65535*(map[i].alpha-low.alpha))/(high.alpha-low.alpha)); + } + liberateMemory(&histogram); + liberateMemory(&map); + + /* + Stretch the histogram. + */ + for(y=0; y < image.height(); ++y){ + q = (unsigned int *)image.scanLine(y); + for(x=0; x < image.width(); ++x){ + if(low.red != high.red) + r = (equalize_map[(unsigned short)(qRed(q[x]))].red/257); + else + r = qRed(q[x]); + if(low.green != high.green) + g = (equalize_map[(unsigned short)(qGreen(q[x]))].green/257); + else + g = qGreen(q[x]); + if(low.blue != high.blue) + b = (equalize_map[(unsigned short)(qBlue(q[x]))].blue/257); + else + b = qBlue(q[x]); + if(low.alpha != high.alpha) + a = (equalize_map[(unsigned short)(qAlpha(q[x]))].alpha/257); + else + a = qAlpha(q[x]); + q[x] = qRgba(r, g, b, a); + } + } + liberateMemory(&equalize_map); + +} + +QImage KImageEffect::edge(QImage &image, double radius) +{ + double *kernel; + int width; + register long i; + QImage dest; + + if(radius == 50.0){ + /* For binary compatability! Remove me when possible! This used to + * take a different parameter, a factor, and this was the default + * value */ + radius = 0.0; + } + + width = getOptimalKernelWidth(radius, 0.5); + if(image.width() < width || image.height() < width){ + qWarning("KImageEffect::edge(): Image is smaller than radius!"); + return(dest); + } + kernel= (double *)malloc(width*width*sizeof(double)); + if(!kernel){ + qWarning("KImageEffect::edge(): Unable to allocate memory!"); + return(dest); + } + for(i=0; i < (width*width); i++) + kernel[i]=(-1.0); + kernel[i/2]=width*width-1.0; + convolveImage(&image, &dest, width, kernel); + free(kernel); + return(dest); +} + +QImage KImageEffect::emboss(QImage &src) +{ + /* binary compat method - remove me when possible! */ + return(emboss(src, 0, 1)); +} + +QImage KImageEffect::emboss(QImage &image, double radius, double sigma) +{ + double alpha, *kernel; + int j, width; + register long i, u, v; + QImage dest; + + if(sigma == 0.0){ + qWarning("KImageEffect::emboss(): Zero sigma is not permitted!"); + return(dest); + } + + width = getOptimalKernelWidth(radius, sigma); + if(image.width() < width || image.height() < width){ + qWarning("KImageEffect::emboss(): Image is smaller than radius!"); + return(dest); + } + kernel= (double *)malloc(width*width*sizeof(double)); + if(!kernel){ + qWarning("KImageEffect::emboss(): Unable to allocate memory!"); + return(dest); + } + if(image.depth() < 32) + image = image.convertDepth(32); + + i=0; + j=width/2; + for(v=(-width/2); v <= (width/2); v++){ + for(u=(-width/2); u <= (width/2); u++){ + alpha=exp(-((double) u*u+v*v)/(2.0*sigma*sigma)); + kernel[i]=((u < 0) || (v < 0) ? -8.0 : 8.0)*alpha/ + (2.0*MagickPI*sigma*sigma); + if (u == j) + kernel[i]=0.0; + i++; + } + j--; + } + convolveImage(&image, &dest, width, kernel); + liberateMemory(&kernel); + + equalize(dest); + return(dest); +} + +void KImageEffect::blurScanLine(double *kernel, int width, + unsigned int *src, unsigned int *dest, + int columns) +{ + register double *p; + unsigned int *q; + register int x; + register long i; + double red, green, blue, alpha; + double scale = 0.0; + + if(width > columns){ + for(x=0; x < columns; ++x){ + scale = 0.0; + red = blue = green = alpha = 0.0; + p = kernel; + q = src; + for(i=0; i < columns; ++i){ + if((i >= (x-width/2)) && (i <= (x+width/2))){ + red += (*p)*(qRed(*q)*257); + green += (*p)*(qGreen(*q)*257); + blue += (*p)*(qBlue(*q)*257); + alpha += (*p)*(qAlpha(*q)*257); + } + if(((i+width/2-x) >= 0) && ((i+width/2-x) < width)) + scale+=kernel[i+width/2-x]; + p++; + q++; + } + scale = 1.0/scale; + red = scale*(red+0.5); + green = scale*(green+0.5); + blue = scale*(blue+0.5); + alpha = scale*(alpha+0.5); + + red = red < 0 ? 0 : red > 65535 ? 65535 : red; + green = green < 0 ? 0 : green > 65535 ? 65535 : green; + blue = blue < 0 ? 0 : blue > 65535 ? 65535 : blue; + alpha = alpha < 0 ? 0 : alpha > 65535 ? 65535 : alpha; + + dest[x] = qRgba((unsigned char)(red/257UL), + (unsigned char)(green/257UL), + (unsigned char)(blue/257UL), + (unsigned char)(alpha/257UL)); + } + return; + } + + for(x=0; x < width/2; ++x){ + scale = 0.0; + red = blue = green = alpha = 0.0; + p = kernel+width/2-x; + q = src; + for(i=width/2-x; i < width; ++i){ + red += (*p)*(qRed(*q)*257); + green += (*p)*(qGreen(*q)*257); + blue += (*p)*(qBlue(*q)*257); + alpha += (*p)*(qAlpha(*q)*257); + scale += (*p); + p++; + q++; + } + scale=1.0/scale; + + red = scale*(red+0.5); + green = scale*(green+0.5); + blue = scale*(blue+0.5); + alpha = scale*(alpha+0.5); + + red = red < 0 ? 0 : red > 65535 ? 65535 : red; + green = green < 0 ? 0 : green > 65535 ? 65535 : green; + blue = blue < 0 ? 0 : blue > 65535 ? 65535 : blue; + alpha = alpha < 0 ? 0 : alpha > 65535 ? 65535 : alpha; + + dest[x] = qRgba((unsigned char)(red/257UL), + (unsigned char)(green/257UL), + (unsigned char)(blue/257UL), + (unsigned char)(alpha/257UL)); + } + + for(; x < columns-width/2; ++x){ + red = blue = green = alpha = 0.0; + p = kernel; + q = src+(x-width/2); + for (i=0; i < (long) width; ++i){ + red += (*p)*(qRed(*q)*257); + green += (*p)*(qGreen(*q)*257); + blue += (*p)*(qBlue(*q)*257); + alpha += (*p)*(qAlpha(*q)*257); + p++; + q++; + } + red = scale*(red+0.5); + green = scale*(green+0.5); + blue = scale*(blue+0.5); + alpha = scale*(alpha+0.5); + + red = red < 0 ? 0 : red > 65535 ? 65535 : red; + green = green < 0 ? 0 : green > 65535 ? 65535 : green; + blue = blue < 0 ? 0 : blue > 65535 ? 65535 : blue; + alpha = alpha < 0 ? 0 : alpha > 65535 ? 65535 : alpha; + + dest[x] = qRgba((unsigned char)(red/257UL), + (unsigned char)(green/257UL), + (unsigned char)(blue/257UL), + (unsigned char)(alpha/257UL)); + } + + for(; x < columns; ++x){ + red = blue = green = alpha = 0.0; + scale=0; + p = kernel; + q = src+(x-width/2); + for(i=0; i < columns-x+width/2; ++i){ + red += (*p)*(qRed(*q)*257); + green += (*p)*(qGreen(*q)*257); + blue += (*p)*(qBlue(*q)*257); + alpha += (*p)*(qAlpha(*q)*257); + scale += (*p); + p++; + q++; + } + scale=1.0/scale; + red = scale*(red+0.5); + green = scale*(green+0.5); + blue = scale*(blue+0.5); + alpha = scale*(alpha+0.5); + + red = red < 0 ? 0 : red > 65535 ? 65535 : red; + green = green < 0 ? 0 : green > 65535 ? 65535 : green; + blue = blue < 0 ? 0 : blue > 65535 ? 65535 : blue; + alpha = alpha < 0 ? 0 : alpha > 65535 ? 65535 : alpha; + + dest[x] = qRgba((unsigned char)(red/257UL), + (unsigned char)(green/257UL), + (unsigned char)(blue/257UL), + (unsigned char)(alpha/257UL)); + } +} + +int KImageEffect::getBlurKernel(int width, double sigma, double **kernel) +{ +#define KernelRank 3 + double alpha, normalize; + register long i; + int bias; + + assert(sigma != 0.0); + if(width == 0) + width = 3; + *kernel=(double *)malloc(width*sizeof(double)); + if(*kernel == (double *)NULL) + return(0); + memset(*kernel, 0, width*sizeof(double)); + bias = KernelRank*width/2; + for(i=(-bias); i <= bias; i++){ + alpha=exp(-((double) i*i)/(2.0*KernelRank*KernelRank*sigma*sigma)); + (*kernel)[(i+bias)/KernelRank]+=alpha/(MagickSQ2PI*sigma); + } + normalize=0; + for(i=0; i < width; i++) + normalize+=(*kernel)[i]; + for(i=0; i < width; i++) + (*kernel)[i]/=normalize; + + return(width); +} + +QImage KImageEffect::blur(QImage &src, double /*factor*/) +{ + /* binary compat method - remove me when possible! */ + return(blur(src, 0, 1)); +} + +QImage KImageEffect::blur(QImage &src, double radius, double sigma) +{ + double *kernel; + QImage dest; + int width; + int x, y; + unsigned int *scanline, *temp; + unsigned int *p, *q; + + if(sigma == 0.0){ + qWarning("KImageEffect::blur(): Zero sigma is not permitted!"); + return(dest); + } + if(src.depth() < 32) + src = src.convertDepth(32); + + kernel=(double *) NULL; + if(radius > 0) + width=getBlurKernel((int) (2*ceil(radius)+1),sigma,&kernel); + else{ + double *last_kernel; + last_kernel=(double *) NULL; + width=getBlurKernel(3,sigma,&kernel); + + while ((long) (MaxRGB*kernel[0]) > 0){ + if(last_kernel != (double *)NULL){ + liberateMemory(&last_kernel); + } + last_kernel=kernel; + kernel = (double *)NULL; + width = getBlurKernel(width+2, sigma, &kernel); + } + if(last_kernel != (double *) NULL){ + liberateMemory(&kernel); + width-=2; + kernel = last_kernel; + } + } + + if(width < 3){ + qWarning("KImageEffect::blur(): Kernel radius is too small!"); + liberateMemory(&kernel); + return(dest); + } + + dest.create(src.width(), src.height(), 32); + + scanline = (unsigned int *)malloc(sizeof(unsigned int)*src.height()); + temp = (unsigned int *)malloc(sizeof(unsigned int)*src.height()); + for(y=0; y < src.height(); ++y){ + p = (unsigned int *)src.scanLine(y); + q = (unsigned int *)dest.scanLine(y); + blurScanLine(kernel, width, p, q, src.width()); + } + + unsigned int **srcTable = (unsigned int **)src.jumpTable(); + unsigned int **destTable = (unsigned int **)dest.jumpTable(); + for(x=0; x < src.width(); ++x){ + for(y=0; y < src.height(); ++y){ + scanline[y] = srcTable[y][x]; + } + blurScanLine(kernel, width, scanline, temp, src.height()); + for(y=0; y < src.height(); ++y){ + destTable[y][x] = temp[y]; + } + } + free(scanline); + free(temp); + free(kernel); + return(dest); +} + +bool KImageEffect::convolveImage(QImage *image, QImage *dest, + const unsigned int order, + const double *kernel) +{ + long width; + double red, green, blue, alpha; + double normalize, *normal_kernel; + register const double *k; + register unsigned int *q; + int x, y, mx, my, sx, sy; + long i; + int mcx, mcy; + + width = order; + if((width % 2) == 0){ + qWarning("KImageEffect: Kernel width must be an odd number!"); + return(false); + } + normal_kernel = (double *)malloc(width*width*sizeof(double)); + if(!normal_kernel){ + qWarning("KImageEffect: Unable to allocate memory!"); + return(false); + } + dest->reset(); + dest->create(image->width(), image->height(), 32); + if(image->depth() < 32) + *image = image->convertDepth(32); + + normalize=0.0; + for(i=0; i < (width*width); i++) + normalize += kernel[i]; + if(fabs(normalize) <= MagickEpsilon) + normalize=1.0; + normalize=1.0/normalize; + for(i=0; i < (width*width); i++) + normal_kernel[i] = normalize*kernel[i]; + + unsigned int **jumpTable = (unsigned int **)image->jumpTable(); + for(y=0; y < dest->height(); ++y){ + sy = y-(width/2); + q = (unsigned int *)dest->scanLine(y); + for(x=0; x < dest->width(); ++x){ + k = normal_kernel; + red = green = blue = alpha = 0; + sy = y-(width/2); + for(mcy=0; mcy < width; ++mcy, ++sy){ + my = sy < 0 ? 0 : sy > image->height()-1 ? + image->height()-1 : sy; + sx = x+(-width/2); + for(mcx=0; mcx < width; ++mcx, ++sx){ + mx = sx < 0 ? 0 : sx > image->width()-1 ? + image->width()-1 : sx; + red += (*k)*(qRed(jumpTable[my][mx])*257); + green += (*k)*(qGreen(jumpTable[my][mx])*257); + blue += (*k)*(qBlue(jumpTable[my][mx])*257); + alpha += (*k)*(qAlpha(jumpTable[my][mx])*257); + ++k; + } + } + + red = red < 0 ? 0 : red > 65535 ? 65535 : red+0.5; + green = green < 0 ? 0 : green > 65535 ? 65535 : green+0.5; + blue = blue < 0 ? 0 : blue > 65535 ? 65535 : blue+0.5; + alpha = alpha < 0 ? 0 : alpha > 65535 ? 65535 : alpha+0.5; + + *q++ = qRgba((unsigned char)(red/257UL), + (unsigned char)(green/257UL), + (unsigned char)(blue/257UL), + (unsigned char)(alpha/257UL)); + } + } + free(normal_kernel); + return(true); + +} + +int KImageEffect::getOptimalKernelWidth(double radius, double sigma) +{ + double normalize, value; + long width; + register long u; + + assert(sigma != 0.0); + if(radius > 0.0) + return((int)(2.0*ceil(radius)+1.0)); + for(width=5; ;){ + normalize=0.0; + for(u=(-width/2); u <= (width/2); u++) + normalize+=exp(-((double) u*u)/(2.0*sigma*sigma))/(MagickSQ2PI*sigma); + u=width/2; + value=exp(-((double) u*u)/(2.0*sigma*sigma))/(MagickSQ2PI*sigma)/normalize; + if((long)(65535*value) <= 0) + break; + width+=2; + } + return((int)width-2); +} + +QImage KImageEffect::sharpen(QImage &src, double /*factor*/) +{ + /* binary compat method - remove me when possible! */ + return(sharpen(src, 0, 1)); +} + +QImage KImageEffect::sharpen(QImage &image, double radius, double sigma) +{ + double alpha, normalize, *kernel; + int width; + register long i, u, v; + QImage dest; + + if(sigma == 0.0){ + qWarning("KImageEffect::sharpen(): Zero sigma is not permitted!"); + return(dest); + } + width = getOptimalKernelWidth(radius, sigma); + if(image.width() < width){ + qWarning("KImageEffect::sharpen(): Image is smaller than radius!"); + return(dest); + } + kernel = (double *)malloc(width*width*sizeof(double)); + if(!kernel){ + qWarning("KImageEffect::sharpen(): Unable to allocate memory!"); + return(dest); + } + + i = 0; + normalize=0.0; + for(v=(-width/2); v <= (width/2); v++){ + for(u=(-width/2); u <= (width/2); u++){ + alpha=exp(-((double) u*u+v*v)/(2.0*sigma*sigma)); + kernel[i]=alpha/(2.0*MagickPI*sigma*sigma); + normalize+=kernel[i]; + i++; + } + } + kernel[i/2]=(-2.0)*normalize; + convolveImage(&image, &dest, width, kernel); + free(kernel); + return(dest); +} + +// End of new algorithms + +QImage KImageEffect::shade(QImage &src, bool color_shading, double azimuth, + double elevation) +{ + struct PointInfo{ + double x, y, z; + }; + + double distance, normal_distance, shade; + int x, y; + + struct PointInfo light, normal; + + unsigned int *q; + + QImage dest(src.width(), src.height(), 32); + + azimuth = DegreesToRadians(azimuth); + elevation = DegreesToRadians(elevation); + light.x = MaxRGB*cos(azimuth)*cos(elevation); + light.y = MaxRGB*sin(azimuth)*cos(elevation); + light.z = MaxRGB*sin(elevation); + normal.z= 2*MaxRGB; // constant Z of surface normal + + if(src.depth() > 8){ // DirectClass source image + unsigned int *p, *s0, *s1, *s2; + for(y=0; y < src.height(); ++y){ + p = (unsigned int *)src.scanLine(QMIN(QMAX(y-1,0),src.height()-3)); + q = (unsigned int *)dest.scanLine(y); + // shade this row of pixels. + *q++=(*(p+src.width())); + p++; + s0 = p; + s1 = p + src.width(); + s2 = p + 2*src.width(); + for(x=1; x < src.width()-1; ++x){ + // determine the surface normal and compute shading. + normal.x=intensityValue(*(s0-1))+intensityValue(*(s1-1))+intensityValue(*(s2-1))- + (double) intensityValue(*(s0+1))-(double) intensityValue(*(s1+1))- + (double) intensityValue(*(s2+1)); + normal.y=intensityValue(*(s2-1))+intensityValue(*s2)+intensityValue(*(s2+1))- + (double) intensityValue(*(s0-1))-(double) intensityValue(*s0)- + (double) intensityValue(*(s0+1)); + if((normal.x == 0) && (normal.y == 0)) + shade=light.z; + else{ + shade=0.0; + distance=normal.x*light.x+normal.y*light.y+normal.z*light.z; + if (distance > 0.0){ + normal_distance= + normal.x*normal.x+normal.y*normal.y+normal.z*normal.z; + if(fabs(normal_distance) > 0.0000001) + shade=distance/sqrt(normal_distance); + } + } + if(!color_shading){ + *q = qRgba((unsigned char)(shade), + (unsigned char)(shade), + (unsigned char)(shade), + qAlpha(*s1)); + } + else{ + *q = qRgba((unsigned char)((shade*qRed(*s1))/(MaxRGB+1)), + (unsigned char)((shade*qGreen(*s1))/(MaxRGB+1)), + (unsigned char)((shade*qBlue(*s1))/(MaxRGB+1)), + qAlpha(*s1)); + } + ++s0; + ++s1; + ++s2; + q++; + } + *q++=(*s1); + } + } + else{ // PsudeoClass source image + unsigned char *p, *s0, *s1, *s2; + int scanLineIdx; + unsigned int *cTable = (unsigned int *)src.colorTable(); + for(y=0; y < src.height(); ++y){ + scanLineIdx = QMIN(QMAX(y-1,0),src.height()-3); + p = (unsigned char *)src.scanLine(scanLineIdx); + q = (unsigned int *)dest.scanLine(y); + // shade this row of pixels. + s0 = p; + s1 = (unsigned char *) src.scanLine(scanLineIdx+1); + s2 = (unsigned char *) src.scanLine(scanLineIdx+2); + *q++=(*(cTable+(*s1))); + ++p; + ++s0; + ++s1; + ++s2; + for(x=1; x < src.width()-1; ++x){ + // determine the surface normal and compute shading. + normal.x=intensityValue(*(cTable+(*(s0-1))))+intensityValue(*(cTable+(*(s1-1))))+intensityValue(*(cTable+(*(s2-1))))- + (double) intensityValue(*(cTable+(*(s0+1))))-(double) intensityValue(*(cTable+(*(s1+1))))- + (double) intensityValue(*(cTable+(*(s2+1)))); + normal.y=intensityValue(*(cTable+(*(s2-1))))+intensityValue(*(cTable+(*s2)))+intensityValue(*(cTable+(*(s2+1))))- + (double) intensityValue(*(cTable+(*(s0-1))))-(double) intensityValue(*(cTable+(*s0)))- + (double) intensityValue(*(cTable+(*(s0+1)))); + if((normal.x == 0) && (normal.y == 0)) + shade=light.z; + else{ + shade=0.0; + distance=normal.x*light.x+normal.y*light.y+normal.z*light.z; + if (distance > 0.0){ + normal_distance= + normal.x*normal.x+normal.y*normal.y+normal.z*normal.z; + if(fabs(normal_distance) > 0.0000001) + shade=distance/sqrt(normal_distance); + } + } + if(!color_shading){ + *q = qRgba((unsigned char)(shade), + (unsigned char)(shade), + (unsigned char)(shade), + qAlpha(*(cTable+(*s1)))); + } + else{ + *q = qRgba((unsigned char)((shade*qRed(*(cTable+(*s1))))/(MaxRGB+1)), + (unsigned char)((shade*qGreen(*(cTable+(*s1))))/(MaxRGB+1)), + (unsigned char)((shade*qBlue(*(cTable+(*s1))))/(MaxRGB+1)), + qAlpha(*s1)); + } + ++s0; + ++s1; + ++s2; + q++; + } + *q++=(*(cTable+(*s1))); + } + } + return(dest); +} + +// High quality, expensive HSV contrast. You can do a faster one by just +// taking a grayscale threshold (ie: 128) and incrementing RGB color +// channels above it and decrementing those below it, but this gives much +// better results. (mosfet 12/28/01) +void KImageEffect::contrastHSV(QImage &img, bool sharpen) +{ + int i, sign; + unsigned int *data; + int count; + double brightness, scale, theta; + QColor c; + int h, s, v; + + sign = sharpen ? 1 : -1; + scale=0.5000000000000001; + if(img.depth() > 8){ + count = img.width()*img.height(); + data = (unsigned int *)img.bits(); + } + else{ + count = img.numColors(); + data = (unsigned int *)img.colorTable(); + } + for(i=0; i < count; ++i){ + c.setRgb(data[i]); + c.hsv(&h, &s, &v); + brightness = v/255.0; + theta=(brightness-0.5)*M_PI; + brightness+=scale*(((scale*((sin(theta)+1.0)))-brightness)*sign); + if (brightness > 1.0) + brightness=1.0; + else + if (brightness < 0) + brightness=0.0; + v = (int)(brightness*255); + c.setHsv(h, s, v); + data[i] = qRgba(c.red(), c.green(), c.blue(), qAlpha(data[i])); + } +} + + +struct BumpmapParams { + BumpmapParams( double bm_azimuth, double bm_elevation, + int bm_depth, KImageEffect::BumpmapType bm_type, + bool invert ) { + /* Convert to radians */ + double azimuth = DegreesToRadians( bm_azimuth ); + double elevation = DegreesToRadians( bm_elevation ); + + /* Calculate the light vector */ + lx = (int)( cos(azimuth) * cos(elevation) * 255.0 ); + ly = (int)( sin(azimuth) * cos(elevation) * 255.0 ); + int lz = (int)( sin(elevation) * 255.0 ); + + /* Calculate constant Z component of surface normal */ + int nz = (6 * 255) / bm_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 (int i = 0; i < 256; i++) + { + double n = 0; + switch (bm_type) + { + case KImageEffect::Spherical: + n = i / 255.0 - 1.0; + lut[i] = (int) (255.0 * sqrt(1.0 - n * n) + 0.5); + break; + + case KImageEffect::Sinuosidal: + 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 KImageEffect::Linear: + default: + lut[i] = i; + } + + if (invert) + lut[i] = 255 - lut[i]; + } + } + int lx, ly; + int nz2, nzlz; + int background; + double compensation; + uchar lut[256]; +}; + + +static void bumpmap_convert_row( uint *row, + int width, + int bpp, + int has_alpha, + uchar *lut, + int waterlevel ) +{ + uint *p; + + p = row; + + has_alpha = has_alpha ? 1 : 0; + + if (bpp >= 3) + for (; width; width--) + { + if (has_alpha) { + unsigned int idx = (unsigned int)(intensityValue( *row ) + 0.5); + *p++ = lut[(unsigned int) ( waterlevel + + ( ( idx - + waterlevel) * qBlue( *row )) / 255.0 )]; + } else { + unsigned int idx = (unsigned int)(intensityValue( *row ) + 0.5); + *p++ = lut[idx]; + } + + ++row; + } +} + +static void bumpmap_row( uint *src, + uint *dest, + int width, + int bpp, + int has_alpha, + uint *bm_row1, + uint *bm_row2, + uint *bm_row3, + int bm_width, + int bm_xofs, + bool tiled, + bool row_in_bumpmap, + int ambient, + bool compensate, + BumpmapParams *params ) +{ + int xofs1, xofs2, xofs3; + int shade; + int ndotl; + int nx, ny; + int x; + int tmp; + + tmp = bm_xofs; + xofs2 = MOD(tmp, bm_width); + + for (x = 0; x < width; x++) + { + /* Calculate surface normal from bump map */ + + if (tiled || (row_in_bumpmap && + x >= - tmp && x < - tmp + bm_width)) { + if (tiled) { + xofs1 = MOD(xofs2 - 1, bm_width); + xofs3 = MOD(xofs2 + 1, bm_width); + } else { + xofs1 = FXCLAMP(xofs2 - 1, 0, bm_width - 1); + xofs3 = FXCLAMP(xofs2 + 1, 0, bm_width - 1); + } + 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 = ny = 0; + } + + /* Shade */ + + if ((nx == 0) && (ny == 0)) + shade = params->background; + else { + ndotl = nx * params->lx + ny * params->ly + params->nzlz; + + if (ndotl < 0) + shade = (int)( params->compensation * ambient ); + else { + shade = (int)( ndotl / sqrt(double(nx * nx + ny * ny + params->nz2)) ); + + shade = (int)( shade + QMAX(0.0, (255 * params->compensation - shade)) * + ambient / 255 ); + } + } + + /* Paint */ + + /** + * NOTE: if we want to work with non-32bit images the alpha handling would + * also change + */ + if (compensate) { + int red = (int)((qRed( *src ) * shade) / (params->compensation * 255)); + int green = (int)((qGreen( *src ) * shade) / (params->compensation * 255)); + int blue = (int)((qBlue( *src ) * shade) / (params->compensation * 255)); + int alpha = (int)((qAlpha( *src ) * shade) / (params->compensation * 255)); + ++src; + *dest++ = qRgba( red, green, blue, alpha ); + } else { + int red = qRed( *src ) * shade / 255; + int green = qGreen( *src ) * shade / 255; + int blue = qBlue( *src ) * shade / 255; + int alpha = qAlpha( *src ) * shade / 255; + ++src; + *dest++ = qRgba( red, green, blue, alpha ); + } + + /* Next pixel */ + + if (++xofs2 == bm_width) + xofs2 = 0; + } +} + +/** + * A bumpmapping algorithm. + * + * @param img the image you want bumpmap + * @param map the map used + * @param azimuth azimuth + * @param elevation elevation + * @param depth depth (not the depth of the image, but of the map) + * @param xofs X offset + * @param yofs Y offset + * @param waterlevel level that full transparency should represent + * @param ambient ambient lighting factor + * @param compensate compensate for darkening + * @param invert invert bumpmap + * @param type type of the bumpmap + * + * @return The destination image (dst) containing the result. + * @author Zack Rusin <zack@kde.org> + */ +QImage KImageEffect::bumpmap(QImage &img, QImage &map, double azimuth, double elevation, + int depth, int xofs, int yofs, int waterlevel, + int ambient, bool compensate, bool invert, + BumpmapType type, bool tiled) +{ + QImage dst; + + if ( img.depth() != 32 || img.depth() != 32 ) { + qWarning( "Bump-mapping effect works only with 32 bit images"); + return dst; + } + + dst.create( img.width(), img.height(), img.depth() ); + int bm_width = map.width(); + int bm_height = map.height(); + int bm_bpp = map.depth(); + int bm_has_alpha = map.hasAlphaBuffer(); + + int yofs1, yofs2, yofs3; + + if ( tiled ) { + yofs2 = MOD( yofs, bm_height ); + yofs1 = MOD( yofs2 - 1, bm_height); + yofs3 = MOD( yofs2 + 1, bm_height); + } else { + yofs1 = 0; + yofs2 = 0; + yofs3 = FXCLAMP( yofs2+1, 0, bm_height - 1 ); + } + + BumpmapParams params( azimuth, elevation, depth, type, invert ); + + uint* bm_row1 = (unsigned int*)map.scanLine( yofs1 ); + uint* bm_row2 = (unsigned int*)map.scanLine( yofs2 ); + uint* bm_row3 = (unsigned int*)map.scanLine( yofs3 ); + + bumpmap_convert_row( bm_row1, bm_width, bm_bpp, bm_has_alpha, params.lut, waterlevel ); + bumpmap_convert_row( bm_row2, bm_width, bm_bpp, bm_has_alpha, params.lut, waterlevel ); + bumpmap_convert_row( bm_row3, bm_width, bm_bpp, bm_has_alpha, params.lut, waterlevel ); + + for (int y = 0; y < img.height(); ++y) + { + int row_in_bumpmap = (y >= - yofs && y < - yofs + bm_height); + + uint* src_row = (unsigned int*)img.scanLine( y ); + uint* dest_row = (unsigned int*)dst.scanLine( y ); + + bumpmap_row( src_row, dest_row, img.width(), img.depth(), img.hasAlphaBuffer(), + bm_row1, bm_row2, bm_row3, bm_width, xofs, + tiled, + row_in_bumpmap, ambient, compensate, + ¶ms ); + + /* Next line */ + + if (tiled || row_in_bumpmap) + { + uint* bm_tmprow = bm_row1; + bm_row1 = bm_row2; + bm_row2 = bm_row3; + bm_row3 = bm_tmprow; + + if (++yofs2 == bm_height) + yofs2 = 0; + + if (tiled) + yofs3 = MOD(yofs2 + 1, bm_height); + else + yofs3 = FXCLAMP(yofs2 + 1, 0, bm_height - 1); + + bm_row3 = (unsigned int*)map.scanLine( yofs3 ); + bumpmap_convert_row( bm_row3, bm_width, bm_bpp, bm_has_alpha, + params.lut, waterlevel ); + } + } + return dst; +} diff --git a/kdefx/kimageeffect.h b/kdefx/kimageeffect.h new file mode 100644 index 000000000..7bca73820 --- /dev/null +++ b/kdefx/kimageeffect.h @@ -0,0 +1,807 @@ +/* This file is part of the KDE libraries + Copyright (C) 1998, 1999, 2001, 2002 Daniel M. Duley <mosfet@interaccess.com> + (C) 1998, 1999 Christian Tibirna <ctibirna@total.net> + (C) 1998, 1999 Dirk Mueller <mueller@kde.org> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +// $Id$ + +#ifndef __KIMAGE_EFFECT_H +#define __KIMAGE_EFFECT_H + +#include <kdelibs_export.h> + +class QImage; +class QSize; +class QColor; +class QPoint; +class QRect; + +/** + * This class includes various QImage based graphical effects. + * + * Everything is + * static, so there is no need to create an instance of this class. You can + * just call the static methods. They are encapsulated here merely to provide + * a common namespace. + */ +class KDEFX_EXPORT KImageEffect +{ +public: + /** + * This enum provides a gradient type specification + * @see KImageEffect::blend(), KImageEffect::gradient(), + * KImageEffect::unbalancedGradient() + */ + enum GradientType { VerticalGradient, + HorizontalGradient, + DiagonalGradient, + CrossDiagonalGradient, + PyramidGradient, + RectangleGradient, + PipeCrossGradient, + EllipticGradient + }; + + /** + * This enum provides a RGB channel specification + * @see KImageEffect::blend(), KImageEffect::channelIntensity(), + * KImageEffect::modulate() + */ + enum RGBComponent { Red, //!< Red channel + Green, //!< Green channel + Blue, //!< Blue channel + Gray, //!< Grey channel + All //!< All channels + }; + + /** + * This enum provides a lighting direction specification + * @see KImageEffect::hash() + */ + enum Lighting {NorthLite, //!< Lighting from the top of the image + NWLite, //!< Lighting from the top left of the image + WestLite, //!< Lighting from the left of the image + SWLite, //!< Lighting from the bottom left of the image + SouthLite, //!< Lighting from the bottom of the image + SELite, //!< Lighting from the bottom right of the image + EastLite, //!< Lighting from the right of the image + NELite //!< Lighting from the top right of the image + }; + + /** + * This enum provides a modulation type specification + * @see KImageEffect::modulate() + */ + enum ModulationType { Intensity, //!< Modulate image intensity + Saturation, //!< Modulate image saturation + HueShift, //!< Modulate image hue + Contrast //!< Modulate image contrast + }; + + /** + * This enum provides a noise type specification + * @see KImageEffect::addNoise() + */ + enum NoiseType { UniformNoise=0, //!< Uniform distribution + GaussianNoise, //!< Gaussian distribution + MultiplicativeGaussianNoise, //!< Multiplicative Gaussian distribution + ImpulseNoise, //!< Impulse distribution + LaplacianNoise, //!< Laplacian distribution + PoissonNoise //!< Poisson distribution + }; + + /** + * This enum provides a rotation specification. + * @see KImageEffect::rotate() + */ + enum RotateDirection{ Rotate90, //!< Rotate 90 degrees to the right. + Rotate180, //!< Rotate 180 degrees. + Rotate270 //!< Rotate 90 degrees to the left. + }; + + /** + * This enum lists possible bumpmapping implementations. + * @see KImageEffect::bumpmap() + */ + enum BumpmapType { + Linear, + Spherical, + Sinuosidal + }; + + /** + * Create a gradient from color a to color b of the specified type. + * + * @param size The desired size of the gradient. + * @param ca Color a + * @param cb Color b + * @param type The type of gradient. + * @param ncols The number of colors to use when not running on a + * truecolor display. The gradient will be dithered to this number of + * colors. Pass 0 to prevent dithering. + */ + static QImage gradient(const QSize &size, const QColor &ca, + const QColor &cb, GradientType type, int ncols=3); + + /** + * Create an unbalanced gradient. + * + * An unbalanced gradient is a gradient where the transition from + * color a to color b is not linear, but in this case, exponential. + * + * @param size The desired size of the gradient. + * @param ca Color a + * @param cb Color b + * @param type The type of gradient. + * @param xfactor The x decay length. Use a value between -200 and 200. + * @param yfactor The y decay length. + * @param ncols The number of colors. See KImageEffect:gradient. + */ + static QImage unbalancedGradient(const QSize &size, const QColor &ca, + const QColor &cb, GradientType type, int xfactor = 100, + int yfactor = 100, int ncols = 3); + + /** + * Blends a color into the destination image, using an opacity + * value for blending one into another. Very fast direct pixel + * manipulation is used. + * + * This function uses MMX and SSE2 instructions to blend the + * image on processors that support it. + * + * @param clr source color to be blended into the destination image. + * @param dst destination image in which the source will be blended into. + * @param opacity opacity (between 0.0 and 1.0) which determines how much + * the source color will be blended into the destination image. + * @return The destination image (dst) containing the result. + * @author Karol Szwed (gallium@kde.org) + * @author Fredrik Höglund (fredrik@kde.org) + */ + static QImage& blend(const QColor& clr, QImage& dst, float opacity); + + /** + * Blend the src image into the destination image, using an opacity + * value for blending one into another. Very fast direct pixel + * manipulation is used. + * + * This function uses MMX and SSE2 instructions to blend the + * images on processors that support it. + * + * @param src source image to be blended into the destination image. + * @param dst destination image in which the source will be blended into. + * @param opacity opacity (between 0.0 and 1.0) which determines how much + * the source image will be blended into the destination image. + * @return The destination image (dst) containing the result. + * @author Karol Szwed (gallium@kde.org) + * @author Fredrik Höglund (fredrik@kde.org) + */ + static QImage& blend(QImage& src, QImage& dst, float opacity); + + /** + * Blend the provided image into a background of the indicated color. + * + * @param initial_intensity this parameter takes values from -1 to 1: + * a) if positive: how much to fade the image in its + * less affected spot + * b) if negative: roughly indicates how much of the image + * remains unaffected + * @param bgnd indicates the color of the background to blend in + * @param eff lets you choose what kind of blending you like + * @param anti_dir blend in the opposite direction (makes no much sense + * with concentric blending effects) + * @param image must be 32bpp + */ + static QImage& blend(QImage &image, float initial_intensity, + const QColor &bgnd, GradientType eff, + bool anti_dir=false); + + /** + * Blend an image into another one, using a gradient type + * for blending from one to another. + * + * @param image1 source1 and result of blending + * @param image2 source2 of blending + * @param gt gradient type for blending between source1 and source2 + * @param xf x decay length for unbalanced gradient tpye + * @param yf y decay length for unbalanced gradient tpye + */ + static QImage& blend(QImage &image1,QImage &image2, + GradientType gt, int xf=100, int yf=100); + + /** + * Blend an image into another one, using a color channel of a + * third image for the decision of blending from one to another. + * + * @param image1 Source 1 and result of blending + * @param image2 Source 2 of blending + * @param blendImage If the gray value of of pixel is 0, the result + * for this pixel is that of image1; for a gray value + * of 1, the pixel of image2 is used; for a value + * in between, a corresponding blending is used. + * @param channel The RBG channel to use for the blending decision. + */ + static QImage& blend(QImage &image1, QImage &image2, + QImage &blendImage, RGBComponent channel); + + /** + * Blend an image into another one, using alpha in the expected way. + * @param upper the "upper" image + * @param lower the "lower" image + * @param output the target image + * @author Rik Hemsley (rikkus) <rik@kde.org> + */ + static bool blend(const QImage & upper, const QImage & lower, QImage & output); +// Not yet... static bool blend(const QImage & image1, const QImage & image2, QImage & output, const QRect & destRect); + + /** + * Blend an image into another one, using alpha in the expected way and + * over coordinates @p x and @p y with respect to the lower image. + * The output is a QImage which is the @p upper image already blended + * with the @p lower one, so its size will be (in general) the same than + * @p upper instead of the same size than @p lower like the method above. + * In fact, the size of @p output is like upper's one only when it can be + * painted on lower, if there has to be some clipping, output's size will + * be the clipped area and x and y will be set to the correct up-left corner + * where the clipped rectangle begins. + * @param x x-coordinate of lower image + * @param y y-coordinate of lower image + * @param upper the "upper" image + * @param lower the "lower" image + * @param output the target image + */ + static bool blend(int &x, int &y, const QImage & upper, const QImage & lower, QImage & output); + + /** + * Blend an image into another one, using alpha in the expected way and + * over coordinates @p x and @p y with respect to the lower image. + * The output is painted in the own @p lower image. This is an optimization + * of the blend method above provided by convenience. + * @param x x-coordinate of lower image + * @param y y-coordinate of lower image + * @param upper the "upper" image + * @param lower the "lower" image, which becomes the output image + */ + static bool blendOnLower(int x, int y, const QImage & upper, const QImage & lower); + + /** + * Blend part of an image into part of another, using the alpha channel in + * the expected way. + * Note that the destination rectangle will be correctly clipped. + * + * @param upper the "upper" image + * @param upperOffset Offset for the part of the upper image to be used. + * @param lower the "lower" image + * @param lowerRect Rectangle for the part of the lower image where the + * blending will occur. + * @since 3.2 + */ + static void blendOnLower(const QImage &upper, const QPoint &upperOffset, + QImage &lower, const QRect &lowerRect); + + /** + * Blend part of an image into part of another, using the opacity value + * and the alpha channel in the expected way. + * Note that the destination rectangle will be correctly clipped. + * + * @param upper the "upper" image + * @param upperOffset Offset for the part of the upper image to be used. + * @param lower the "lower" image + * @param lowerRect Rectangle for the part of the lower image where the + * blending will occur. + * @param opacity Opacity (between 0.0 and 1.0) which determines how much + * the source image will be blended into the destination image. + * @since 3.2 + */ + static void blendOnLower(const QImage &upper, const QPoint &upperOffset, + QImage &lower, const QRect &lowerRect, float opacity); + + /** + * Disposition of a source image on top of a destination image. + * @see KImageEffect::computeDestinationRect, KImageEffect::blendOnLower + * @since 3.2 + */ + enum Disposition { NoImage = 0, //!< Don't overlay + Centered, //!< Center top image on botton image + Tiled, //!< Tile top image on bottom image + CenterTiled, //!< Center and tile top image on bottom image + CenteredMaxpect, //!< Center and scale aspect + TiledMaxpect, //!< Tile and scale aspect + Scaled, //!< Scale + CenteredAutoFit //!< Center and scale or scale aspect + }; + + /** + * Compute the destination rectangle where to draw the upper image on top + * of another image using the given disposition. For tiled + * disposition, the rectangle should be duplicated on the whole area to + * obtained the wanted effect. + * + * @param lowerSize The size of the destination image. + * @param disposition The wanted disposition. + * @param upper The upper image. Note that this image may be scaled to + * adjust to the requested disposition. + * + * @return the computed rectangle. Its size may exceed @e lowerSize. + * @since 3.2 + */ + static QRect computeDestinationRect(const QSize &lowerSize, + Disposition disposition, QImage &upper); + + /** + * Blend an image on top of another using a given disposition and a given + * opacity. The alpha channel of the upper image is used in the expected + * way. Beware the upper image may be modified. + * @since 3.2 + */ + static void blendOnLower(QImage &upper, QImage &lower, + Disposition disposition, float opacity); + + /** + * Modifies the intensity of a pixmap's RGB channel component. + * + * @param image The QImage to process. + * @param percent Percent value. Use a negative value to dim. + * @param channel Which channel(s) should be modified + * @return The @p image, provided for convenience. + * @author Daniel M. Duley (mosfet) + */ + static QImage& channelIntensity(QImage &image, float percent, + RGBComponent channel); + + /** + * Fade an image to a certain background color. + * + * The number of colors will not be changed. + * + * @param image The QImage to process. + * @param val The strength of the effect. 0 <= val <= 1. + * @param color The background color. + * @return Returns the image(), provided for convenience. + */ + static QImage& fade(QImage &image, float val, const QColor &color); + + + /** + * This recolors a pixmap. The most dark color will become color a, + * the most bright one color b, and in between. + * + * @param image A QImage to process. + * @param ca Color a + * @param cb Color b + * @param ncols The number of colors to dither the image to. + * Pass 0 to prevent dithering. + */ + static QImage& flatten(QImage &image, const QColor &ca, + const QColor &cb, int ncols=0); + + /** + * Build a hash on any given QImage + * + * @param image The QImage to process + * @param lite The hash faces the indicated lighting (cardinal poles). + * @param spacing How many unmodified pixels in between hashes. + * @return Returns the image(), provided for convenience. + */ + static QImage& hash(QImage &image, Lighting lite=NorthLite, + unsigned int spacing=0); + + /** + * Either brighten or dim the image by a specified percent. + * For example, .50 will modify the colors by 50%. + * + * This function uses MMX instructions to process the image + * on processors that support it. + * + * @param image The QImage to process. + * @param percent The percent value. Use a negative value to dim. + * @return Returns The image(), provided for convenience. + * @author Daniel M. Duley (mosfet) + * @author Benjamin Roe (ben@benroe.com) + */ + static QImage& intensity(QImage &image, float percent); + + /** + * Modulate the image with a color channel of another image. + * + * @param image The QImage to modulate and result. + * @param modImage The QImage to use for modulation. + * @param reverse Invert the meaning of image/modImage; result is image! + * @param type The modulation Type to use. + * @param factor The modulation amplitude; with 0 no effect [-200;200]. + * @param channel The RBG channel of image2 to use for modulation. + * @return Returns the image(), provided for convenience. + */ + static QImage& modulate(QImage &image, QImage &modImage, bool reverse, + ModulationType type, int factor, RGBComponent channel); + + /** + * Convert an image to grayscale. + * + * @param image The QImage to process. + * @param fast Set to @p true in order to use a faster but non-photographic + * quality algorithm. Appropriate for things such as toolbar icons. + * @return Returns the image(), provided for convenience. + * @author Daniel M. Duley (mosfet) + */ + static QImage& toGray(QImage &image, bool fast = false); + + /** + * Desaturate an image evenly. + * + * @param image The QImage to process. + * @param desat A value between 0 and 1 setting the degree of desaturation + * @return Returns the image(), provided for convenience. + */ + static QImage& desaturate(QImage &image, float desat = 0.3); + + /** + * Fast, but low quality contrast of an image. Also see contrastHSV. + * + * @param image The QImage to process. + * @param c A contrast value between -255 to 255. + * @return The image(), provided for convenience. + * @author Daniel M. Duley (mosfet) + * ### KDE 4: remove + */ + static QImage& contrast(QImage &image, int c); + + /** + * Dither an image using Floyd-Steinberg dithering for low-color + * situations. + * + * @param image The QImage to process. + * @param palette The color palette to use + * @param size The size of the palette + * @return Returns the image(), provided for convenience. + */ + static QImage& dither(QImage &image, const QColor *palette, int size); + + /** + * Calculate the image for a selected image, for instance a selected icon + * on the desktop. + * @param img the QImage to select + * @param col the selected color, usually from QColorGroup::highlight(). + */ + static QImage& selectedImage( QImage &img, const QColor &col ); + + /** + * High quality, expensive HSV contrast. You can do a faster one by just + * taking a intensity threshold (ie: 128) and incrementing RGB color + * channels above it and decrementing those below it, but this gives much + * better results. + * + * @param img The QImage to process. + * @param sharpen If true sharpness is increase, (spiffed). Otherwise + * it is decreased, (dulled). + * @author Daniel M. Duley (mosfet) + */ + static void contrastHSV(QImage &img, bool sharpen=true); + + /** + * Normalises the pixel values to span the full range of color values. + * This is a contrast enhancement technique. + * @param img the image that is normalised + * @author Daniel M. Duley (mosfet) + */ + static void normalize(QImage &img); + + /** + * Performs histogram equalisation on the reference + * image. + * @param img the image that is equalised + * @author Daniel M. Duley (mosfet) + */ + static void equalize(QImage &img); + + /** + * Thresholds the reference image. You can also threshold images by using + * ThresholdDither in the various QPixmap/QImage convert methods, but this + * lets you specify a threshold value. + * + * @param img The QImage to process. + * @param value The threshold value. + * @author Daniel M. Duley (mosfet) + */ + static void threshold(QImage &img, unsigned int value=128); + + /** + * Produces a 'solarization' effect seen when exposing a photographic + * film to light during the development process. + * + * @param img The QImage to process. + * @param factor The extent of the solarization (0-99.9) + * @author Daniel M. Duley (mosfet) + */ + static void solarize(QImage &img, double factor=50.0); + + /** + * Embosses the source image. This involves highlighting the edges + * and applying various other enhancements in order to get a metal + * effect. + * + * @param src The QImage to process. + * @param radius The radius of the gaussian not counting the + * center pixel. Use 0 and a suitable radius will be automatically used. + * @param sigma The standard deviation of the gaussian. Use 1 if you're not + * sure. + * @return The embossed image. The original is not changed. + * @author Daniel M. Duley (mosfet) + */ + static QImage emboss(QImage &src, double radius, double sigma); + + /** + * Convenience method. + */ + static QImage emboss(QImage &src); + + /** + * Minimizes speckle noise in the source image using the 8 hull + * algorithm. + * + * @param src The QImage to process. + * @return The despeckled image. The original is not changed. + * @author Daniel M. Duley (mosfet) + */ + static QImage despeckle(QImage &src); + + /** + * Produces a neat little "charcoal" effect. + * + * @param src The QImage to process. + * @param radius The radius of the gaussian not counting the + * center pixel. Use 0 and a suitable radius will be automatically used. + * @param sigma The standard deviation of the gaussian. Use 1 if you're not + * sure. + * @return The charcoal image. The original is not changed. + * @author Daniel M. Duley (mosfet) + */ + static QImage charcoal(QImage &src, double radius, double sigma); + + /** + * This is provided for binary compatability only! Use the above method + * with a radius and sigma instead! + */ + static QImage charcoal(QImage &src, double factor=50.0); + + /** + * Rotates the image by the specified amount + * + * @param src The QImage to process. + * @param r The rotate direction. + * @return The rotated image. The original is not changed. + * @author Daniel M. Duley (mosfet) + */ + static QImage rotate(QImage &src, RotateDirection r); + + /** + * Scales an image using simple pixel sampling. This does not produce + * nearly as nice a result as QImage::smoothScale(), but has the + * advantage of being much faster - only a few milliseconds. + * + * @param src The QImage to process. + * @param w The new width. + * @param h The new height. + * @return The scaled image. The original is not changed. + * @author Daniel M. Duley (mosfet) + */ + static QImage sample(QImage &src, int w, int h); + + /** + * Adds noise to an image. + * + * @param src The QImage to process. + * @param type The algorithm used to generate the noise. + * @return The image with noise added. The original is not changed. + * @author Daniel M. Duley (mosfet) + */ + static QImage addNoise(QImage &src, NoiseType type = GaussianNoise); + + /** + * Blurs an image by convolving pixel neighborhoods. + * + * @param src The QImage to process. + * @param radius The radius of the gaussian not counting the + * center pixel. Use 0 and a suitable radius will be automatically used. + * @param sigma The standard deviation of the gaussian. Use 1 if you're not + * sure. + * @return The blurred image. The original is not changed. + * @author Daniel M. Duley (mosfet) + */ + static QImage blur(QImage &src, double radius, double sigma); + + /** + * This is provided for binary compatability only! Use the above method + * with a radius and sigma instead! + */ + static QImage blur(QImage &src, double factor=50.0); + + /** + * Detects edges in an image using pixel neighborhoods and an edge + * detection mask. + * + * @param src The QImage to process. + * @param radius The radius of the gaussian not counting the + * center pixel. Use 0 and a suitable radius will be automatically used. + * @return The image with edges detected. The original is not changed. + * @author Daniel M. Duley (mosfet) + */ + static QImage edge(QImage &src, double radius); + + /** + * Implodes an image by a specified percent. + * + * @param src The QImage to process. + * @param factor The extent of the implosion. + * @param background An RGBA value to use for the background. After the + * effect some pixels may be "empty". This value is used for those pixels. + * @return The imploded image. The original is not changed. + * @author Daniel M. Duley (mosfet) + */ + static QImage implode(QImage &src, double factor=30.0, + unsigned int background = 0xFFFFFFFF); + + /** + * Produces an oil painting effect. + * + * @param src The QImage to process. + * @param radius The radius of the gaussian not counting the + * center pixel. Use 0 and a suitable radius will be automatically used. + * @return The new image. The original is not changed. + * @author Daniel M. Duley (mosfet) + */ + static QImage oilPaintConvolve(QImage &src, double radius); + + /** + * This is provided for binary compatability only! Use the above method + * instead! + */ + static QImage oilPaint(QImage &src, int radius=3); + + /** + * Sharpens the pixels in the image using pixel neighborhoods. + * + * @param src The QImage to process. + * @param radius The radius of the gaussian not counting the + * center pixel. Use 0 and a suitable radius will be automatically used. + * @param sigma The standard deviation of the gaussian. Use 1 if you're not + * sure. + * @return The sharpened image. The original is not changed. + * @author Daniel M. Duley (mosfet) + */ + static QImage sharpen(QImage &src, double radius, double sigma); + + /** + * This is provided for binary compatability only! Use the above method + * instead! + */ + static QImage sharpen(QImage &src, double factor=30.0); + + /** + * Randomly displaces pixels. + * + * @param src The QImage to process. + * @param amount The vicinity for choosing a random pixel to swap. + * @return The image with pixels displaced. The original is not changed. + * @author Daniel M. Duley (mosfet) + */ + static QImage spread(QImage &src, unsigned int amount=3); + + /** + * Shades the image using a distance light source. + * + * @param src The QImage to process. + * @param color_shading If true do color shading, otherwise do grayscale. + * @param azimuth Determines the light source and direction. + * @param elevation Determines the light source and direction. + * @return The shaded image. The original is not changed. + * @author Daniel M. Duley (mosfet) + */ + static QImage shade(QImage &src, bool color_shading=true, double azimuth=30.0, + double elevation=30.0); + /** + * Swirls the image by a specified amount + * + * @param src The QImage to process. + * @param degrees The tightness of the swirl. + * @param background An RGBA value to use for the background. After the + * effect some pixels may be "empty". This value is used for those pixels. + * @return The swirled image. The original is not changed. + * @author Daniel M. Duley (mosfet) + */ + static QImage swirl(QImage &src, double degrees=50.0, unsigned int background = + 0xFFFFFFFF); + + /** + * Modifies the pixels along a sine wave. + * + * @param src The QImage to process. + * @param amplitude The amplitude of the sine wave. + * @param frequency The frequency of the sine wave. + * @param background An RGBA value to use for the background. After the + * effect some pixels may be "empty". This value is used for those pixels. + * @return The new image. The original is not changed. + * @author Daniel M. Duley (mosfet) + */ + static QImage wave(QImage &src, double amplitude=25.0, double frequency=150.0, + unsigned int background = 0xFFFFFFFF); + + /** + * A bumpmapping algorithm. + * + * @param img the image you want bumpmap + * @param map the map used + * @param azimuth azimuth + * @param elevation elevation + * @param depth depth (not the depth of the image, but of the map) + * @param xofs X offset + * @param yofs Y offset + * @param waterlevel level that full transparency should represent + * @param ambient ambient lighting factor + * @param compensate compensate for darkening + * @param invert invert bumpmap + * @param type type of the bumpmap + * @param tiled tile the bumpmap over the image through the Y offset + * + * @return The destination image (dst) containing the result. + * @author Zack Rusin <zack@kde.org> + */ + static QImage bumpmap(QImage &img, QImage &map, double azimuth, double elevation, + int depth, int xofs, int yofs, int waterlevel, + int ambient, bool compensate, bool invert, + BumpmapType type, bool tiled); + +private: + + /** + * Helper function to fast calc some altered (lighten, shaded) colors + * + */ + static unsigned int lHash(unsigned int c); + static unsigned int uHash(unsigned int c); + + /** + * Helper function to find the nearest color to the RBG triplet + */ + static int nearestColor( int r, int g, int b, const QColor *pal, int size ); + + static void hull(const int x_offset, const int y_offset, const int polarity, + const int width, const int height, + unsigned int *f, unsigned int *g); + static unsigned int generateNoise(unsigned int pixel, NoiseType type); + static unsigned int interpolateColor(QImage *image, double x, double y, + unsigned int background); + /* Various convolve routines */ + static int getOptimalKernelWidth(double radius, double sigma); + static bool convolveImage(QImage *image, QImage *dest, + const unsigned int order, + const double *kernel); + static void blurScanLine(double *kernel, int width, + unsigned int *src, unsigned int *dest, + int columns); + static int getBlurKernel(int width, double sigma, double **kernel); +}; + +#endif diff --git a/kdefx/kpixmap.cpp b/kdefx/kpixmap.cpp new file mode 100644 index 000000000..9d7b186bd --- /dev/null +++ b/kdefx/kpixmap.cpp @@ -0,0 +1,389 @@ +/* + * This file is part of the KDE libraries + * Copyright (C) 1998 Mark Donohoe <donohoe@kde.org> + * Stephan Kulow <coolo@kde.org> + * + * $Id$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * 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 <qpixmap.h> +#include <qpainter.h> +#include <qimage.h> +#include <qbitmap.h> +#include <qcolor.h> + +#include <stdlib.h> +#include "kpixmap.h" + +// Fast diffuse dither to 3x3x3 color cube +// Based on Qt's image conversion functions +static bool kdither_32_to_8( const QImage *src, QImage *dst ) +{ + // register QRgb *p; + uchar *b; + int y; + + if ( !dst->create(src->width(), src->height(), 8, 256) ) { + qWarning("KPixmap: destination image not valid\n"); + return false; + } + + dst->setNumColors( 256 ); + +#define MAX_R 2 +#define MAX_G 2 +#define MAX_B 2 +#define INDEXOF(r,g,b) (((r)*(MAX_G+1)+(g))*(MAX_B+1)+(b)) + + int rc, gc, bc; + + for ( rc=0; rc<=MAX_R; rc++ ) // build 2x2x2 color cube + for ( gc=0; gc<=MAX_G; gc++ ) + for ( bc=0; bc<=MAX_B; bc++ ) { + dst->setColor( INDEXOF(rc,gc,bc), + qRgb( rc*255/MAX_R, gc*255/MAX_G, bc*255/MAX_B ) ); + } + + int sw = src->width(); + int* line1[3]; + int* line2[3]; + int* pv[3]; + + line1[0] = new int[src->width()]; + line2[0] = new int[src->width()]; + line1[1] = new int[src->width()]; + line2[1] = new int[src->width()]; + line1[2] = new int[src->width()]; + line2[2] = new int[src->width()]; + pv[0] = new int[sw]; + pv[1] = new int[sw]; + pv[2] = new int[sw]; + + for ( y=0; y < src->height(); y++ ) { + // p = (QRgb *)src->scanLine(y); + b = dst->scanLine(y); + int endian = (QImage::systemBitOrder() == QImage::BigEndian); + int x; + uchar* q = src->scanLine(y); + uchar* q2 = src->scanLine(y+1 < src->height() ? y + 1 : 0); + + for (int chan = 0; chan < 3; chan++) { + b = dst->scanLine(y); + int *l1 = (y&1) ? line2[chan] : line1[chan]; + int *l2 = (y&1) ? line1[chan] : line2[chan]; + if ( y == 0 ) { + for (int i=0; i<sw; i++) + l1[i] = q[i*4+chan+endian]; + } + if ( y+1 < src->height() ) { + for (int i=0; i<sw; i++) + l2[i] = q2[i*4+chan+endian]; + } + + // Bi-directional error diffusion + if ( y&1 ) { + for (x=0; x<sw; x++) { + int pix = QMAX(QMIN(2, (l1[x] * 2 + 128)/ 255), 0); + int err = l1[x] - pix * 255 / 2; + pv[chan][x] = pix; + + // Spread the error around... + if ( x+1<sw ) { + l1[x+1] += (err*7)>>4; + l2[x+1] += err>>4; + } + l2[x]+=(err*5)>>4; + if (x>1) + l2[x-1]+=(err*3)>>4; + } + } else { + for (x=sw; x-->0; ) { + int pix = QMAX(QMIN(2, (l1[x] * 2 + 128)/ 255), 0); + int err = l1[x] - pix * 255 / 2; + pv[chan][x] = pix; + + // Spread the error around... + if ( x > 0 ) { + l1[x-1] += (err*7)>>4; + l2[x-1] += err>>4; + } + l2[x]+=(err*5)>>4; + if (x+1 < sw) + l2[x+1]+=(err*3)>>4; + } + } + } + + if (!endian) { + for (x=0; x<sw; x++) + *b++ = INDEXOF(pv[2][x],pv[1][x],pv[0][x]); + } else { + for (x=0; x<sw; x++) + *b++ = INDEXOF(pv[0][x],pv[1][x],pv[2][x]); + } + + } + + delete [] line1[0]; + delete [] line2[0]; + delete [] line1[1]; + delete [] line2[1]; + delete [] line1[2]; + delete [] line2[2]; + delete [] pv[0]; + delete [] pv[1]; + delete [] pv[2]; + +#undef MAX_R +#undef MAX_G +#undef MAX_B +#undef INDEXOF + + return true; +} + +KPixmap::~KPixmap() +{ +} + +bool KPixmap::load( const QString& fileName, const char *format, + int conversion_flags ) +{ + QImageIO io( fileName, format ); + + bool result = io.read(); + + if ( result ) { + detach(); + result = convertFromImage( io.image(), conversion_flags ); + } + return result; +} + +bool KPixmap::load( const QString& fileName, const char *format, + ColorMode mode ) +{ + int conversion_flags = 0; + switch (mode) { + case Color: + conversion_flags |= ColorOnly; + break; + case Mono: + conversion_flags |= MonoOnly; + break; + case LowColor: + conversion_flags |= LowOnly; + break; + case WebColor: + conversion_flags |= WebOnly; + break; + default: + break;// Nothing. + } + return load( fileName, format, conversion_flags ); +} + +bool KPixmap::convertFromImage( const QImage &img, ColorMode mode ) +{ + int conversion_flags = 0; + switch (mode) { + case Color: + conversion_flags |= ColorOnly; + break; + case Mono: + conversion_flags |= MonoOnly; + break; + case LowColor: + conversion_flags |= LowOnly; + break; + case WebColor: + conversion_flags |= WebOnly; + break; + default: + break; // Nothing. + } + return convertFromImage( img, conversion_flags ); +} + +bool KPixmap::convertFromImage( const QImage &img, int conversion_flags ) +{ + if ( img.isNull() ) { +#if defined(CHECK_NULL) + qWarning( "KPixmap::convertFromImage: Cannot convert a null image" ); +#endif + return false; + } + detach(); // detach other references + + int dd = defaultDepth(); + + // If color mode not one of KPixmaps extra modes nothing to do + if ( ( conversion_flags & KColorMode_Mask ) != LowOnly && + ( conversion_flags & KColorMode_Mask ) != WebOnly ) { + return QPixmap::convertFromImage ( img, conversion_flags ); + } + + // If the default pixmap depth is not 8bpp, KPixmap color modes have no + // effect. Ignore them and use AutoColor instead. + if ( dd > 8 ) { + if ( ( conversion_flags & KColorMode_Mask ) == LowOnly || + ( conversion_flags & KColorMode_Mask ) == WebOnly ) + conversion_flags = (conversion_flags & ~KColorMode_Mask) | Auto; + return QPixmap::convertFromImage ( img, conversion_flags ); + } + + if ( ( conversion_flags & KColorMode_Mask ) == LowOnly ) { + // Here we skimp a little on the possible conversion modes + // Don't offer ordered or threshold dither of RGB channels or + // diffuse or ordered dither of alpha channel. It hardly seems + // worth the effort for this specialized mode. + + // If image uses icon palette don't dither it. + if( img.numColors() > 0 && img.numColors() <=40 ) { + if ( checkColorTable( img ) ) + return QPixmap::convertFromImage( img, QPixmap::Auto ); + } + + QBitmap mask; + bool isMask = false; + + QImage image = img.convertDepth(32); + QImage tImage( image.width(), image.height(), 8, 256 ); + + if( img.hasAlphaBuffer() ) { + image.setAlphaBuffer( true ); + tImage.setAlphaBuffer( true ); + isMask = mask.convertFromImage( img.createAlphaMask() ); + } + + kdither_32_to_8( &image, &tImage ); + + if( QPixmap::convertFromImage( tImage ) ) { + if ( isMask ) QPixmap::setMask( mask ); + return true; + } else + return false; + } else { + QImage image = img.convertDepth( 32 ); + image.setAlphaBuffer( img.hasAlphaBuffer() ); + conversion_flags = (conversion_flags & ~ColorMode_Mask) | Auto; + return QPixmap::convertFromImage ( image, conversion_flags ); + } +} + +static QColor* kpixmap_iconPalette = 0; + +bool KPixmap::checkColorTable( const QImage &image ) +{ + int i = 0; + + if (kpixmap_iconPalette == 0) { + kpixmap_iconPalette = new QColor[40]; + + // Standard palette + kpixmap_iconPalette[i++] = red; + kpixmap_iconPalette[i++] = green; + kpixmap_iconPalette[i++] = blue; + kpixmap_iconPalette[i++] = cyan; + kpixmap_iconPalette[i++] = magenta; + kpixmap_iconPalette[i++] = yellow; + kpixmap_iconPalette[i++] = darkRed; + kpixmap_iconPalette[i++] = darkGreen; + kpixmap_iconPalette[i++] = darkBlue; + kpixmap_iconPalette[i++] = darkCyan; + kpixmap_iconPalette[i++] = darkMagenta; + kpixmap_iconPalette[i++] = darkYellow; + kpixmap_iconPalette[i++] = white; + kpixmap_iconPalette[i++] = lightGray; + kpixmap_iconPalette[i++] = gray; + kpixmap_iconPalette[i++] = darkGray; + kpixmap_iconPalette[i++] = black; + + // Pastels + kpixmap_iconPalette[i++] = QColor( 255, 192, 192 ); + kpixmap_iconPalette[i++] = QColor( 192, 255, 192 ); + kpixmap_iconPalette[i++] = QColor( 192, 192, 255 ); + kpixmap_iconPalette[i++] = QColor( 255, 255, 192 ); + kpixmap_iconPalette[i++] = QColor( 255, 192, 255 ); + kpixmap_iconPalette[i++] = QColor( 192, 255, 255 ); + + // Reds + kpixmap_iconPalette[i++] = QColor( 64, 0, 0 ); + kpixmap_iconPalette[i++] = QColor( 192, 0, 0 ); + + // Oranges + kpixmap_iconPalette[i++] = QColor( 255, 128, 0 ); + kpixmap_iconPalette[i++] = QColor( 192, 88, 0 ); + kpixmap_iconPalette[i++] = QColor( 255, 168, 88 ); + kpixmap_iconPalette[i++] = QColor( 255, 220, 168 ); + + // Blues + kpixmap_iconPalette[i++] = QColor( 0, 0, 192 ); + + // Turquoise + kpixmap_iconPalette[i++] = QColor( 0, 64, 64 ); + kpixmap_iconPalette[i++] = QColor( 0, 192, 192 ); + + // Yellows + kpixmap_iconPalette[i++] = QColor( 64, 64, 0 ); + kpixmap_iconPalette[i++] = QColor( 192, 192, 0 ); + + // Greens + kpixmap_iconPalette[i++] = QColor( 0, 64, 0 ); + kpixmap_iconPalette[i++] = QColor( 0, 192, 0 ); + + // Purples + kpixmap_iconPalette[i++] = QColor( 192, 0, 192 ); + + // Greys + kpixmap_iconPalette[i++] = QColor( 88, 88, 88 ); + kpixmap_iconPalette[i++] = QColor( 48, 48, 48 ); + kpixmap_iconPalette[i++] = QColor( 220, 220, 220 ); + + } + + QRgb* ctable = image.colorTable(); + + int ncols = image.numColors(); + int j; + + // Allow one failure which could be transparent background + int failures = 0; + + for ( i=0; i<ncols; i++ ) { + for ( j=0; j<40; j++ ) { + if ( kpixmap_iconPalette[j].red() == qRed( ctable[i] ) && + kpixmap_iconPalette[j].green() == qGreen( ctable[i] ) && + kpixmap_iconPalette[j].blue() == qBlue( ctable[i] ) ) { + break; + } + } + + if ( j == 40 ) { + failures ++; + } + } + + return ( failures <= 1 ); + +} + +KPixmap::KPixmap(const QPixmap& p) + : QPixmap(p) +{ +} diff --git a/kdefx/kpixmap.h b/kdefx/kpixmap.h new file mode 100644 index 000000000..9a1af03bb --- /dev/null +++ b/kdefx/kpixmap.h @@ -0,0 +1,213 @@ +/* + * This file is part of the KDE libraries + * Copyright (C) 1998 Mark Donohoe <donohoe@kde.org> + * Stephan Kulow <coolo@kde.org> + * + * $Id$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * 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. + */ + +#ifndef __KPIXMAP_H__ +#define __KPIXMAP_H__ + +#include <qpixmap.h> + +#include <kdelibs_export.h> + +const int KColorMode_Mask = 0x00000300; +const int WebOnly = 0x00000200; +const int LowOnly = 0x00000300; + +class KPixmapPrivate; + +/** + * Off-screen paint device with extended features. + + * KPixmap has two new color modes, WebColor and LowColor, applicable + * to 8bpp displays. + + * In WebColor mode all images are dithered to the Netscape palette, + * even when they have their own color table. WebColor is the default + * mode for KPixmap so that standard applications can share the Netscape + * palette across the desktop. + + * In LowColor mode images are checked to see if their color table + * matches the KDE icon palette. If the color tables do not match, the + * images are dithered to a minimal 3x3x3 color cube. LowColor mode can + * be used to load icons, background images etc. so that components of + * the desktop which are always present use no more than 40 colors. + + * @author Mark Donohoe (donohoe@kde.org) + * @version $Id$ + */ +class KDEFX_EXPORT KPixmap : public QPixmap +{ +public: + /** + * This enumeration provides a color pallete specification + * @see KPixmap::convertFromImage(), KPixmap::load() + */ + enum ColorMode { Auto, //!< Convert to monochrome if possible + Color, //!< Native display depth + Mono, //!< Monochrome pixmap + LowColor, //!< 3x3x3 color cube (or monochrome) + WebColor //!< Netscape pallete (or monochrome) + }; + /** + * This enumeration provides a gradient mode specification + */ + enum GradientMode { Horizontal, + Vertical, + Diagonal, + CrossDiagonal + }; + + /** + * Constructs a null pixmap. + */ + KPixmap() : QPixmap() {}; + + /** + * Destructs the pixmap. + * ### KDE 4: remove + */ + ~KPixmap(); + + /** + * Copies the QPixmap @p pix. + */ + KPixmap(const QPixmap& pix); + + /** + * Converts an image and sets this pixmap. + * + * The conversion_flags argument is a bitwise-OR from the + * following choices. The options marked (default) are the + * choice if no other choice from the list is included (they + * are zero): + * + * Color/Mono preference + * + * @li WebColor - If the image has depth 1 and contains + * only black and white pixels then the pixmap becomes monochrome. If + * the pixmap has a depth of 8 bits per pixel then the Netscape + * palette is used for the pixmap color table. + * @li LowColor - If the image has depth 1 and contains only black and + * white pixels then the pixmap becomes monochrome. If the pixmap has a + * depth of 8 bits per pixel and the image does not posess a color table + * that matches the Icon palette a 3x3x3 color cube is used for the + * pixmap color table. + * @li AutoColor (default) - If the image has depth 1 and contains + * only black and white pixels, then the pixmap becomes + * monochrome. + * @li ColorOnly - The pixmap is dithered/converted to the native + * display depth. + * @li MonoOnly - The pixmap becomes monochrome. If necessary, it + * is dithered using the chosen dithering algorithm. + * + * Dithering mode preference, for RGB channels + * + * @li DiffuseDither (default) - A high quality dither. + * @li OrderedDither - A faster more ordered dither. + * @li ThresholdDither - No dithering, closest color is used. + * + * Dithering mode preference, for alpha channel + * + * @li DiffuseAlphaDither - A high quality dither. + * @li OrderedAlphaDither - A faster more ordered dither. + * @li ThresholdAlphaDither (default) - No dithering. + * + * Color matching versus dithering preference + * + * @li PreferDither - Always dither 32-bit images when the image + * is being converted to 8-bits. This is the default when + * converting to a pixmap. + * @li AvoidDither - Only dither 32-bit images if the image has + * more than 256 colors and it is being converted to 8-bits. + * This is the default when an image is converted for the + * purpose of saving to a file. + * + * Passing 0 for @p conversion_flags gives all the default + * options. + * + * @param img the image to convert + * @param conversion_flags bitmask, described above + * @return @p true if successful. + **/ + bool convertFromImage( const QImage &img, int conversion_flags ); + + /** + * This is an overloaded member function, provided for + * convenience. It differs from the above function only in + * what argument(s) it accepts. + * @param img the image to convert + * @param mode a ColorMode to apply + * @return @p true if successful. + **/ + bool convertFromImage( const QImage &img, ColorMode mode = WebColor ); + + /** + * Loads a pixmap from the file @p fileName. + * + * If format is specified, the loader attempts to read the + * pixmap using the specified format. If format is not + * specified (default), the loader reads a few bytes from the + * header to guess the file format. + * + * The QImageIO documentation lists the supported image + * formats and explains how to add extra formats. + * + * @param fileName the name of the file to load the image from + * @param format the format for the image + * @param conversion_flags a bitmask, as described in + * convertFromImage() + * @return @p true if successful, or false if the pixmap + * could not be loaded. + **/ + bool load( const QString& fileName, const char *format, + int conversion_flags ); + + /** + * This is an overloaded member function, provided for + * convenience. It differs from the above function only in + * what argument(s) it accepts. + * @param fileName the name of the file to load the image from + * @param format the format for the image + * @param mode a ColorMode to apply + * @return @p true if successful, or false if the pixmap + * could not be loaded. + **/ + bool load( const QString& fileName, + const char *format = 0, + ColorMode mode = WebColor ); + + /** + * Returns true if the image posesses a color table that + * matches the Icon palette or false otherwise. + * + * An image with one color not found in the Icon palette is + * considered to be a match, since this extra color may be a + * transparent background. + * @param image the image to test + **/ + bool checkColorTable(const QImage &image); + +private: + KPixmapPrivate *d; +}; + +#endif diff --git a/kdefx/kpixmapeffect.cpp b/kdefx/kpixmapeffect.cpp new file mode 100644 index 000000000..5184c323e --- /dev/null +++ b/kdefx/kpixmapeffect.cpp @@ -0,0 +1,325 @@ +/* This file is part of the KDE libraries + Copyright (C) 1998, 1999 Christian Tibirna <ctibirna@total.net> + (C) 1998, 1999 Daniel M. Duley <mosfet@kde.org> + (C) 1998, 1999 Dirk A. Mueller <mueller@kde.org> + +*/ + +// $Id$ + +#include <qimage.h> +#include <qpainter.h> + +#include "kpixmapeffect.h" +#include "kpixmap.h" +#include "kimageeffect.h" + +//====================================================================== +// +// Gradient effects +// +//====================================================================== + + +KPixmap& KPixmapEffect::gradient(KPixmap &pixmap, const QColor &ca, + const QColor &cb, GradientType eff, int ncols) +{ + if(pixmap.depth() > 8 && + (eff == VerticalGradient || eff == HorizontalGradient)) { + + int rDiff, gDiff, bDiff; + int rca, gca, bca /*, rcb, gcb, bcb*/; + + register int x, y; + + rDiff = (/*rcb = */ cb.red()) - (rca = ca.red()); + gDiff = (/*gcb = */ cb.green()) - (gca = ca.green()); + bDiff = (/*bcb = */ cb.blue()) - (bca = ca.blue()); + + register int rl = rca << 16; + register int gl = gca << 16; + register int bl = bca << 16; + + int rcdelta = ((1<<16) / (eff == VerticalGradient ? pixmap.height() : pixmap.width())) * rDiff; + int gcdelta = ((1<<16) / (eff == VerticalGradient ? pixmap.height() : pixmap.width())) * gDiff; + int bcdelta = ((1<<16) / (eff == VerticalGradient ? pixmap.height() : pixmap.width())) * bDiff; + + QPainter p(&pixmap); + + // these for-loops could be merged, but the if's in the inner loop + // would make it slow + switch(eff) { + case VerticalGradient: + for ( y = 0; y < pixmap.height(); y++ ) { + rl += rcdelta; + gl += gcdelta; + bl += bcdelta; + + p.setPen(QColor(rl>>16, gl>>16, bl>>16)); + p.drawLine(0, y, pixmap.width()-1, y); + } + break; + case HorizontalGradient: + for( x = 0; x < pixmap.width(); x++) { + rl += rcdelta; + gl += gcdelta; + bl += bcdelta; + + p.setPen(QColor(rl>>16, gl>>16, bl>>16)); + p.drawLine(x, 0, x, pixmap.height()-1); + } + break; + default: + ; + } + } + else { + QImage image = KImageEffect::gradient(pixmap.size(), ca, cb, + (KImageEffect::GradientType) eff, ncols); + pixmap.convertFromImage(image); + } + + return pixmap; +} + + +// ----------------------------------------------------------------------------- + +KPixmap& KPixmapEffect::unbalancedGradient(KPixmap &pixmap, const QColor &ca, + const QColor &cb, GradientType eff, int xfactor, int yfactor, + int ncols) +{ + QImage image = KImageEffect::unbalancedGradient(pixmap.size(), ca, cb, + (KImageEffect::GradientType) eff, + xfactor, yfactor, ncols); + pixmap.convertFromImage(image); + + return pixmap; +} + + +//====================================================================== +// +// Intensity effects +// +//====================================================================== + + + +KPixmap& KPixmapEffect::intensity(KPixmap &pixmap, float percent) +{ + QImage image = pixmap.convertToImage(); + KImageEffect::intensity(image, percent); + pixmap.convertFromImage(image); + + return pixmap; +} + + +// ----------------------------------------------------------------------------- + +KPixmap& KPixmapEffect::channelIntensity(KPixmap &pixmap, float percent, + RGBComponent channel) +{ + QImage image = pixmap.convertToImage(); + KImageEffect::channelIntensity(image, percent, + (KImageEffect::RGBComponent) channel); + pixmap.convertFromImage(image); + + return pixmap; +} + + +//====================================================================== +// +// Blend effects +// +//====================================================================== + + +KPixmap& KPixmapEffect::blend(KPixmap &pixmap, float initial_intensity, + const QColor &bgnd, GradientType eff, + bool anti_dir, int ncols) +{ + + QImage image = pixmap.convertToImage(); + if (image.depth() <=8) + image = image.convertDepth(32); //Sloww.. + + KImageEffect::blend(image, initial_intensity, bgnd, + (KImageEffect::GradientType) eff, anti_dir); + + unsigned int tmp; + + if(pixmap.depth() <= 8 ) { + if ( ncols < 2 || ncols > 256 ) + ncols = 3; + QColor *dPal = new QColor[ncols]; + for (int i=0; i<ncols; i++) { + tmp = 0 + 255 * i / ( ncols - 1 ); + dPal[i].setRgb ( tmp, tmp, tmp ); + } + KImageEffect::dither(image, dPal, ncols); + pixmap.convertFromImage(image); + delete [] dPal; + } + else + pixmap.convertFromImage(image); + + return pixmap; +} + + +//====================================================================== +// +// Hash effects +// +//====================================================================== + +KPixmap& KPixmapEffect::hash(KPixmap &pixmap, Lighting lite, + unsigned int spacing, int ncols) +{ + QImage image = pixmap.convertToImage(); + KImageEffect::hash(image, (KImageEffect::Lighting) lite, spacing); + + unsigned int tmp; + + if(pixmap.depth() <= 8 ) { + if ( ncols < 2 || ncols > 256 ) + ncols = 3; + QColor *dPal = new QColor[ncols]; + for (int i=0; i<ncols; i++) { + tmp = 0 + 255 * i / ( ncols - 1 ); + dPal[i].setRgb ( tmp, tmp, tmp ); + } + KImageEffect::dither(image, dPal, ncols); + pixmap.convertFromImage(image); + delete [] dPal; + } + else + pixmap.convertFromImage(image); + + return pixmap; +} + + +//====================================================================== +// +// Pattern effects +// +//====================================================================== + +#if 0 +void KPixmapEffect::pattern(KPixmap &pixmap, const QColor &ca, + const QColor &cb, unsigned pat[8]) +{ + QImage img = pattern(pixmap.size(), ca, cb, pat); + pixmap.convertFromImage(img); +} +#endif + +// ----------------------------------------------------------------------------- + +KPixmap KPixmapEffect::pattern(const KPixmap& pmtile, QSize size, + const QColor &ca, const QColor &cb, int ncols) +{ + if (pmtile.depth() > 8) + ncols = 0; + + QImage img = pmtile.convertToImage(); + KImageEffect::flatten(img, ca, cb, ncols); + KPixmap pixmap; + pixmap.convertFromImage(img); + + return KPixmapEffect::createTiled(pixmap, size); +} + + +// ----------------------------------------------------------------------------- + +KPixmap KPixmapEffect::createTiled(const KPixmap& pixmap, QSize size) +{ + KPixmap pix(size); + + QPainter p(&pix); + p.drawTiledPixmap(0, 0, size.width(), size.height(), pixmap); + + return pix; +} + + +//====================================================================== +// +// Fade effects +// +//====================================================================== + +KPixmap& KPixmapEffect::fade(KPixmap &pixmap, double val, const QColor &color) +{ + QImage img = pixmap.convertToImage(); + KImageEffect::fade(img, val, color); + pixmap.convertFromImage(img); + + return pixmap; +} + + +// ----------------------------------------------------------------------------- +KPixmap& KPixmapEffect::toGray(KPixmap &pixmap, bool fast) +{ + QImage img = pixmap.convertToImage(); + KImageEffect::toGray(img, fast); + pixmap.convertFromImage(img); + + return pixmap; +} + +// ----------------------------------------------------------------------------- +KPixmap& KPixmapEffect::desaturate(KPixmap &pixmap, float desat) +{ + QImage img = pixmap.convertToImage(); + KImageEffect::desaturate(img, desat); + pixmap.convertFromImage(img); + + return pixmap; +} +// ----------------------------------------------------------------------------- +KPixmap& KPixmapEffect::contrast(KPixmap &pixmap, int c) +{ + QImage img = pixmap.convertToImage(); + KImageEffect::contrast(img, c); + pixmap.convertFromImage(img); + + return pixmap; +} + +//====================================================================== +// +// Dither effects +// +//====================================================================== + +// ----------------------------------------------------------------------------- +KPixmap& KPixmapEffect::dither(KPixmap &pixmap, const QColor *palette, int size) +{ + QImage img = pixmap.convertToImage(); + KImageEffect::dither(img, palette, size); + pixmap.convertFromImage(img); + + return pixmap; +} + +//====================================================================== +// +// Other effects +// +//====================================================================== + +KPixmap KPixmapEffect::selectedPixmap( const KPixmap &pix, const QColor &col ) +{ + QImage img = pix.convertToImage(); + KImageEffect::selectedImage(img, col); + KPixmap outPix; + outPix.convertFromImage(img); + return outPix; +} diff --git a/kdefx/kpixmapeffect.h b/kdefx/kpixmapeffect.h new file mode 100644 index 000000000..a4776f0e6 --- /dev/null +++ b/kdefx/kpixmapeffect.h @@ -0,0 +1,218 @@ +/* This file is part of the KDE libraries + Copyright (C) 1998, 1999 Christian Tibirna <ctibirna@total.net> + (C) 1998, 1999 Daniel M. Duley <mosfet@kde.org> + (C) 1998, 1999 Dirk Mueller <mueller@kde.org> + +*/ + +// $Id$ + +#ifndef __KPIXMAP_EFFECT_H +#define __KPIXMAP_EFFECT_H + +#include <kdelibs_export.h> + +#include <qsize.h> +class KPixmap; +class QColor; + +/** + * This class includes various pixmap-based graphical effects. + * + * Everything is + * static, so there is no need to create an instance of this class. You can + * just call the static methods. They are encapsulated here merely to provide + * a common namespace. + */ +class KDEFX_EXPORT KPixmapEffect +{ +public: + enum GradientType { VerticalGradient, HorizontalGradient, + DiagonalGradient, CrossDiagonalGradient, + PyramidGradient, RectangleGradient, + PipeCrossGradient, EllipticGradient }; + enum RGBComponent { Red, Green, Blue }; + + enum Lighting {NorthLite, NWLite, WestLite, SWLite, + SouthLite, SELite, EastLite, NELite}; + + /** + * Creates a gradient from color a to color b of the specified type. + * + * @param pixmap The pixmap to process. + * @param ca Color a. + * @param cb Color b. + * @param type The type of gradient. + * @param ncols The number of colors to use when not running on a + * truecolor display. The gradient will be dithered to this number of + * colors. Pass 0 to prevent dithering. + * @return Returns the generated pixmap, for convenience. + */ + static KPixmap& gradient(KPixmap& pixmap, const QColor &ca, const QColor &cb, + GradientType type, int ncols=3); + + /** + * Creates an unbalanced gradient. + * + * An unbalanced gradient is a gradient where the transition from + * color a to color b is not linear, but in this case, exponential. + * + * @param pixmap The pixmap that should be written. + * @param ca Color a. + * @param cb Color b. + * @param type The type of gradient. + * @param xfactor The x decay length. Use a value between -200 and 200. + * @param yfactor The y decay length. + * @param ncols The number of colors. See #gradient. + * @return The generated pixmap, for convencience. + */ + static KPixmap& unbalancedGradient(KPixmap& pixmap, const QColor &ca, + const QColor &cb, GradientType type, int xfactor = 100, + int yfactor = 100, int ncols=3); + + /** + * Creates a pixmap of a given size with the given pixmap. + * + * if the + * given size is bigger than the size of the pixmap, the pixmap is + * tiled. + * + * @param pixmap This is the source pixmap + * @param size The size the new pixmap should have. + * @return The generated, tiled pixmap. + */ + static KPixmap createTiled(const KPixmap& pixmap, QSize size); + + /** + * Either brightens or dims a pixmap by a specified ratio. + * + * @param pixmap The pixmap to process. + * @param ratio The ratio to use. Use negative value to dim. + * @return Returns The pixmap(), provided for convenience. + */ + static KPixmap& intensity(KPixmap& pixmap, float ratio); + + /** + * Modifies the intensity of a pixmap's RGB channel component. + * + * @param pixmap The pixmap to process. + * @param ratio value. Use negative value to dim. + * @param channel Which channel(s) should be modified + * @return Returns the pixmap(), provided for convenience. + */ + static KPixmap& channelIntensity(KPixmap& pixmap, float ratio, + RGBComponent channel); + + /** + * Blends the provided pixmap into a background of the indicated color. + * + * @param pixmap The pixmap to process. + * @param initial_intensity this parameter takes values from -1 to 1: + * @li If positive, it tells how much to fade the image in its + * less affected spot. + * @li If negative, it tells roughly indicates how much of the image + * remains unaffected + * @param bgnd Indicates the color of the background to blend in. + * @param eff Lets you choose what kind of blending you like. + * @param anti_dir Blend in the opposite direction (makes no much sense + * with concentric blending effects). + * @param ncols The number of colors to dither the pixmap to. Only + * used for 8 bpp pixmaps. + * @return Returns the pixmap(), provided for convenience. + */ + static KPixmap& blend(KPixmap& pixmap, float initial_intensity, + const QColor &bgnd, GradientType eff, + bool anti_dir=false, int ncols=3); + + /** + * Builds a hash on any given pixmap. + * + * @param pixmap The pixmap to process. + * @param lite The hash faces the indicated lighting (cardinal poles) + * @param spacing How many unmodified pixels inbetween hashes. + * @param ncols The number of colors to dither the pixmap to. + * Only used for 8 bpp pixmaps. + * @return Returns The pixmap(), provided for convenience. + */ + static KPixmap& hash(KPixmap& pixmap, Lighting lite=NorthLite, + unsigned int spacing=0, int ncols=3); + + /** + * Creates a pattern from a pixmap. + * + * The given pixmap is "flattened" + * between color a to color b. + * Doesn't change the original pixmap. + * + * @param pixmap The pixmap to process. + * @param size The size of the returned pixmap. If @p size is larger than + * the original, the resulting pixmap will be tiled. + * @param ca Color a. + * @param cb Color b. + * @param ncols The number of colors to use. The image will be + * dithered to this depth. Pass zero to prevent dithering. + * @return The resulting pixmap. + */ + static KPixmap pattern(const KPixmap& pixmap, QSize size, + const QColor &ca, const QColor &cb, int ncols=8); + + /** + * Fades a pixmap to a certain color. + * + * @param pixmap The pixmap to process. + * @param val The strength of the effect. 0 <= val <= 1. + * @param color The color to blend to. + * @return Returns the pixmap(), provided for convenience. + */ + static KPixmap& fade(KPixmap& pixmap, double val, const QColor &color); + + /** + * Converts a pixmap to grayscale. + * + * @param pixmap The pixmap to process. + * @param fast Set to @p true in order to use a faster but non-photographic + * quality algorithm. Appropriate for things such as toolbar icons. + * @return Returns the pixmap(), provided for convenience. + */ + static KPixmap& toGray(KPixmap& pixmap, bool fast=false); + + /** + * Desaturates a pixmap. + * + * @param pixmap The pixmap to process. + * @param desat A value between 0 and 1 setting the degree of desaturation + * @return Returns The pixmap(), provided for convenience. + */ + static KPixmap& desaturate(KPixmap& pixmap, float desat = 0.3); + + /** + * Modifies the contrast of a pixmap. + * + * @param pixmap The pixmap to process. + * @param c A contrast value between -255 and 255. + * @return Returns the pixmap(), provided for convenience. + */ + static KPixmap& contrast(KPixmap& pixmap, int c); + + /** + * Dithers a pixmap using Floyd-Steinberg dithering for low-color + * situations. + * + * @param pixmap The pixmap to process. + * @param palette The color palette to use. + * @param size The size of the palette. + * @return Returns the pixmap(), provided for convenience. + */ + static KPixmap& dither(KPixmap &pixmap, const QColor *palette, int size); + + /** + * Calculate a 'selected' pixmap, for instance a selected icon + * on the desktop. + * @param pixmap the pixmap to select + * @param col the selected color, usually from QColorGroup::highlight(). + */ + static KPixmap selectedPixmap( const KPixmap &pixmap, const QColor &col ); +}; + + +#endif diff --git a/kdefx/kpixmapsplitter.cpp b/kdefx/kpixmapsplitter.cpp new file mode 100644 index 000000000..83fc3de5b --- /dev/null +++ b/kdefx/kpixmapsplitter.cpp @@ -0,0 +1,95 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 Carsten Pfeiffer <pfeiffer@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 "kpixmapsplitter.h" + +KPixmapSplitter::KPixmapSplitter() + : m_itemSize( 4, 7 ), + m_vSpacing( 0 ), + m_hSpacing( 0 ), + m_numCols( 0 ), + m_numRows( 0 ), + m_dirty( false ) +{ +} + +KPixmapSplitter::~KPixmapSplitter() +{ +} + +void KPixmapSplitter::setPixmap( const QPixmap& pixmap ) +{ + m_pixmap = pixmap; + m_dirty = true; +} + +void KPixmapSplitter::setItemSize( const QSize& size ) +{ + if ( size != m_itemSize ) { + m_itemSize = size; + m_dirty = true; + } +} + +void KPixmapSplitter::setVSpacing( int spacing ) +{ + if ( spacing != m_vSpacing ) { + m_vSpacing = spacing; + m_dirty = true; + } +} + +void KPixmapSplitter::setHSpacing( int spacing ) +{ + if ( spacing != m_hSpacing ) { + m_hSpacing = spacing; + m_dirty = true; + } +} + + +QRect KPixmapSplitter::coordinates( int pos ) +{ + if ( pos < 0 || m_pixmap.isNull() ) + return QRect(); + + if ( m_dirty ) { + m_numCols = m_pixmap.width() / ( m_itemSize.width() + m_hSpacing ); + m_numRows = m_pixmap.height() / ( m_itemSize.height() + m_vSpacing ); + m_dirty = false; + // qDebug("cols: %i, rows: %i (pixmap: %i, %i)", m_numCols, m_numRows, m_pixmap.width(), m_pixmap.height()); + } + + if ( m_numCols == 0 || m_numRows == 0 ) + return QRect(); + + int row = pos / m_numCols; + int col = pos - (row * m_numCols); + + return QRect( col * (m_itemSize.width() + m_hSpacing), + row * (m_itemSize.height() + m_vSpacing), + m_itemSize.width(), + m_itemSize.height() ); +} + +QRect KPixmapSplitter::coordinates( const QChar& ch ) +{ + return coordinates( (unsigned char) ch.latin1() ); +} + diff --git a/kdefx/kpixmapsplitter.h b/kdefx/kpixmapsplitter.h new file mode 100644 index 000000000..6c525c172 --- /dev/null +++ b/kdefx/kpixmapsplitter.h @@ -0,0 +1,123 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 Carsten Pfeiffer <pfeiffer@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef KPIXMAPSPLITTER_H +#define KPIXMAPSPLITTER_H + +#include <qpixmap.h> +#include <qrect.h> +#include <qsize.h> +#include <qstring.h> + +#include <kdelibs_export.h> + +class KPixmapSplitterPrivate; +/** + * @short A class to split a pixmap into several items. + * + * If you have a pixmap containing several items (icons), you can use this + * class to get the coordinates of each item. + * + * For example, if you have a pixmap with 25 items and you want to get the + * 4th item as a pixmap (every item being 20x10 pixels): + * \code + * KPixmapSplitter splitter; + * splitter.setPixmap( somePixmap ); + * splitter.setItemSize( QSize( 20, 10 )); + * + * QPixmap item( 20, 10 ); + * item.fill( Qt::white ); + * QRect rect = splitter.coordinates( 4 ); + * if ( !rect.isEmpty() ) + * bitBlt( &item, QPoint(0,0), &somePixmap, rect, CopyROP ); + * \endcode + * + * @author Carsten Pfeiffer <pfeiffer@kde.org> + */ +class KDEFX_EXPORT KPixmapSplitter +{ +public: + /** + * Constructor, does nothing but initialize some default-values. + */ + KPixmapSplitter(); + ~KPixmapSplitter(); + + /** + * Sets the pixmap to be split. + */ + void setPixmap( const QPixmap& pixmap ); + + /** + * @returns the pixmap that has been set via setPixmap(). + */ + const QPixmap& pixmap() const { return m_pixmap; } + + /** + * Sets the size of the items you want to get out of the given pixmap. + * The QRect of #coordinates(int) will have the width and height of exactly + * this @p size. + */ + void setItemSize( const QSize& size ); + + /** + * @returns the set size of the items (coordinates) you want to get + * out of the given pixmap. + */ + QSize itemSize() const { return m_itemSize; } + + /** + * If there is space between rows in the given pixmap, you have to specify + * how many pixels there are. + */ + void setVSpacing( int spacing ); + + /** + * If there is space between columns in the given pixmap, you have to + * specify how many pixels there are. + */ + void setHSpacing( int spacing ); + + /** + * @returns the coordinates of the item at position pos in the given + * pixmap. + */ + QRect coordinates( int pos ); + + /** + * Overloaded for convenience. Returns the item at the position of the + * given character (when using a latin1 font-pixmap) + */ + QRect coordinates( const QChar& ch ); + +private: + QPixmap m_pixmap; + QSize m_itemSize; + + int m_vSpacing; + int m_hSpacing; + + int m_numCols; + int m_numRows; + + bool m_dirty; + KPixmapSplitterPrivate* d; +}; + +#endif // KPIXMAPSPLITTER_H diff --git a/kdefx/kstyle.cpp b/kdefx/kstyle.cpp new file mode 100644 index 000000000..5ce02012b --- /dev/null +++ b/kdefx/kstyle.cpp @@ -0,0 +1,2164 @@ +/* + * + * KStyle + * Copyright (C) 2001-2002 Karol Szwed <gallium@kde.org> + * + * QWindowsStyle CC_ListView and style images were kindly donated by TrollTech, + * Copyright (C) 1998-2000 TrollTech AS. + * + * Many thanks to Bradley T. Hughes for the 3 button scrollbar code. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "kstyle.h" + +#include <qapplication.h> +#include <qbitmap.h> +#include <qcleanuphandler.h> +#include <qmap.h> +#include <qimage.h> +#include <qlistview.h> +#include <qmenubar.h> +#include <qpainter.h> +#include <qpixmap.h> +#include <qpopupmenu.h> +#include <qprogressbar.h> +#include <qscrollbar.h> +#include <qsettings.h> +#include <qslider.h> +#include <qstylefactory.h> +#include <qtabbar.h> +#include <qtoolbar.h> + +#include <kpixmap.h> +#include <kpixmapeffect.h> +#include <kimageeffect.h> + +#ifdef Q_WS_X11 +# include <X11/Xlib.h> +# ifdef HAVE_XRENDER +# include <X11/extensions/Xrender.h> // schroder + extern bool qt_use_xrender; +# endif +#else +#undef HAVE_XRENDER +#endif + + +#include <limits.h> + +namespace +{ + // INTERNAL + enum TransparencyEngine { + Disabled = 0, + SoftwareTint, + SoftwareBlend, + XRender + }; + + // Drop Shadow + struct ShadowElements { + QWidget* w1; + QWidget* w2; + }; + typedef QMap<const QPopupMenu*,ShadowElements> ShadowMap; + static ShadowMap *_shadowMap = 0; + QSingleCleanupHandler<ShadowMap> cleanupShadowMap; + ShadowMap &shadowMap() { + if ( !_shadowMap ) { + _shadowMap = new ShadowMap; + cleanupShadowMap.set( &_shadowMap ); + } + return *_shadowMap; + } + + + // DO NOT ASK ME HOW I MADE THESE TABLES! + // (I probably won't remember anyway ;) + const double top_right_corner[16] = + { 0.949, 0.965, 0.980, 0.992, + 0.851, 0.890, 0.945, 0.980, + 0.706, 0.780, 0.890, 0.960, + 0.608, 0.706, 0.851, 0.949 }; + + const double bottom_right_corner[16] = + { 0.608, 0.706, 0.851, 0.949, + 0.706, 0.780, 0.890, 0.960, + 0.851, 0.890, 0.945, 0.980, + 0.949, 0.965, 0.980, 0.992 }; + + const double bottom_left_corner[16] = + { 0.949, 0.851, 0.706, 0.608, + 0.965, 0.890, 0.780, 0.706, + 0.980, 0.945, 0.890, 0.851, + 0.992, 0.980, 0.960, 0.949 }; + + const double shadow_strip[4] = + { 0.565, 0.675, 0.835, 0.945 }; +} + + +namespace +{ +class TransparencyHandler : public QObject +{ + public: + TransparencyHandler(KStyle* style, TransparencyEngine tEngine, + float menuOpacity, bool useDropShadow); + ~TransparencyHandler(); + bool eventFilter(QObject* object, QEvent* event); + + protected: + void blendToColor(const QColor &col); + void blendToPixmap(const QColorGroup &cg, const QPopupMenu* p); +#ifdef HAVE_XRENDER + void XRenderBlendToPixmap(const QPopupMenu* p); +#endif + void createShadowWindows(const QPopupMenu* p); + void removeShadowWindows(const QPopupMenu* p); + void rightShadow(QImage& dst); + void bottomShadow(QImage& dst); + private: + bool dropShadow; + float opacity; + QPixmap pix; + KStyle* kstyle; + TransparencyEngine te; +}; +} // namespace + +struct KStylePrivate +{ + bool highcolor : 1; + bool useFilledFrameWorkaround : 1; + bool etchDisabledText : 1; + bool scrollablePopupmenus : 1; + bool menuAltKeyNavigation : 1; + bool menuDropShadow : 1; + bool sloppySubMenus : 1; + int popupMenuDelay; + float menuOpacity; + + TransparencyEngine transparencyEngine; + KStyle::KStyleScrollBarType scrollbarType; + TransparencyHandler* menuHandler; + KStyle::KStyleFlags flags; + + //For KPE_ListViewBranch + QBitmap *verticalLine; + QBitmap *horizontalLine; +}; + +// ----------------------------------------------------------------------------- + + +KStyle::KStyle( KStyleFlags flags, KStyleScrollBarType sbtype ) + : QCommonStyle(), d(new KStylePrivate) +{ + d->flags = flags; + bool useMenuTransparency = (flags & AllowMenuTransparency); + d->useFilledFrameWorkaround = (flags & FilledFrameWorkaround); + d->scrollbarType = sbtype; + d->highcolor = QPixmap::defaultDepth() > 8; + + // Read style settings + QSettings settings; + d->popupMenuDelay = settings.readNumEntry ("/KStyle/Settings/PopupMenuDelay", 256); + d->sloppySubMenus = settings.readBoolEntry("/KStyle/Settings/SloppySubMenus", false); + d->etchDisabledText = settings.readBoolEntry("/KStyle/Settings/EtchDisabledText", true); + d->menuAltKeyNavigation = settings.readBoolEntry("/KStyle/Settings/MenuAltKeyNavigation", true); + d->scrollablePopupmenus = settings.readBoolEntry("/KStyle/Settings/ScrollablePopupMenus", false); + d->menuDropShadow = settings.readBoolEntry("/KStyle/Settings/MenuDropShadow", false); + d->menuHandler = NULL; + + if (useMenuTransparency) { + QString effectEngine = settings.readEntry("/KStyle/Settings/MenuTransparencyEngine", "Disabled"); + +#ifdef HAVE_XRENDER + if (effectEngine == "XRender") + d->transparencyEngine = XRender; +#else + if (effectEngine == "XRender") + d->transparencyEngine = SoftwareBlend; +#endif + else if (effectEngine == "SoftwareBlend") + d->transparencyEngine = SoftwareBlend; + else if (effectEngine == "SoftwareTint") + d->transparencyEngine = SoftwareTint; + else + d->transparencyEngine = Disabled; + + if (d->transparencyEngine != Disabled) { + // Create an instance of the menu transparency handler + d->menuOpacity = settings.readDoubleEntry("/KStyle/Settings/MenuOpacity", 0.90); + d->menuHandler = new TransparencyHandler(this, d->transparencyEngine, + d->menuOpacity, d->menuDropShadow); + } + } + + d->verticalLine = 0; + d->horizontalLine = 0; + + // Create a transparency handler if only drop shadows are enabled. + if (!d->menuHandler && d->menuDropShadow) + d->menuHandler = new TransparencyHandler(this, Disabled, 1.0, d->menuDropShadow); +} + + +KStyle::~KStyle() +{ + delete d->verticalLine; + delete d->horizontalLine; + + delete d->menuHandler; + + d->menuHandler = NULL; + delete d; +} + + +QString KStyle::defaultStyle() +{ + if (QPixmap::defaultDepth() > 8) + return QString("plastik"); + else + return QString("light, 3rd revision"); +} + + +void KStyle::polish( QWidget* widget ) +{ + if ( d->useFilledFrameWorkaround ) + { + if ( QFrame *frame = ::qt_cast< QFrame* >( widget ) ) { + QFrame::Shape shape = frame->frameShape(); + if (shape == QFrame::ToolBarPanel || shape == QFrame::MenuBarPanel) + widget->installEventFilter(this); + } + } +} + + +void KStyle::unPolish( QWidget* widget ) +{ + if ( d->useFilledFrameWorkaround ) + { + if ( QFrame *frame = ::qt_cast< QFrame* >( widget ) ) { + QFrame::Shape shape = frame->frameShape(); + if (shape == QFrame::ToolBarPanel || shape == QFrame::MenuBarPanel) + widget->removeEventFilter(this); + } + } +} + + +// Style changes (should) always re-polish popups. +void KStyle::polishPopupMenu( QPopupMenu* p ) +{ + if (!p->testWState( WState_Polished )) + p->setCheckable(true); + + // Install transparency handler if the effect is enabled. + if ( d->menuHandler && + (strcmp(p->name(), "tear off menu") != 0)) + p->installEventFilter(d->menuHandler); +} + + +// ----------------------------------------------------------------------------- +// KStyle extensions +// ----------------------------------------------------------------------------- + +void KStyle::setScrollBarType(KStyleScrollBarType sbtype) +{ + d->scrollbarType = sbtype; +} + +KStyle::KStyleFlags KStyle::styleFlags() const +{ + return d->flags; +} + +void KStyle::renderMenuBlendPixmap( KPixmap &pix, const QColorGroup &cg, + const QPopupMenu* /* popup */ ) const +{ + pix.fill(cg.button()); // Just tint as the default behavior +} + + +void KStyle::drawKStylePrimitive( KStylePrimitive kpe, + QPainter* p, + const QWidget* widget, + const QRect &r, + const QColorGroup &cg, + SFlags flags, + const QStyleOption& /* opt */ ) const +{ + switch( kpe ) + { + // Dock / Toolbar / General handles. + // --------------------------------- + + case KPE_DockWindowHandle: { + + // Draws a nice DockWindow handle including the dock title. + QWidget* wid = const_cast<QWidget*>(widget); + bool horizontal = flags & Style_Horizontal; + int x,y,w,h,x2,y2; + + r.rect( &x, &y, &w, &h ); + if ((w <= 2) || (h <= 2)) { + p->fillRect(r, cg.highlight()); + return; + } + + + x2 = x + w - 1; + y2 = y + h - 1; + + QFont fnt; + fnt = QApplication::font(wid); + fnt.setPointSize( fnt.pointSize()-2 ); + + // Draw the item on an off-screen pixmap + // to preserve Xft antialiasing for + // vertically oriented handles. + QPixmap pix; + if (horizontal) + pix.resize( h-2, w-2 ); + else + pix.resize( w-2, h-2 ); + + QString title = wid->parentWidget()->caption(); + QPainter p2; + p2.begin(&pix); + p2.fillRect(pix.rect(), cg.brush(QColorGroup::Highlight)); + p2.setPen(cg.highlightedText()); + p2.setFont(fnt); + p2.drawText(pix.rect(), AlignCenter, title); + p2.end(); + + // Draw a sunken bevel + p->setPen(cg.dark()); + p->drawLine(x, y, x2, y); + p->drawLine(x, y, x, y2); + p->setPen(cg.light()); + p->drawLine(x+1, y2, x2, y2); + p->drawLine(x2, y+1, x2, y2); + + if (horizontal) { + QWMatrix m; + m.rotate(-90.0); + QPixmap vpix = pix.xForm(m); + bitBlt(wid, r.x()+1, r.y()+1, &vpix); + } else + bitBlt(wid, r.x()+1, r.y()+1, &pix); + + break; + } + + + /* + * KPE_ListViewExpander and KPE_ListViewBranch are based on code from + * QWindowStyle's CC_ListView, kindly donated by TrollTech. + * CC_ListView code is Copyright (C) 1998-2000 TrollTech AS. + */ + + case KPE_ListViewExpander: { + // Typical Windows style expand/collapse element. + int radius = (r.width() - 4) / 2; + int centerx = r.x() + r.width()/2; + int centery = r.y() + r.height()/2; + + // Outer box + p->setPen( cg.mid() ); + p->drawRect( r ); + + // plus or minus + p->setPen( cg.text() ); + p->drawLine( centerx - radius, centery, centerx + radius, centery ); + if ( flags & Style_On ) // Collapsed = On + p->drawLine( centerx, centery - radius, centerx, centery + radius ); + break; + } + + case KPE_ListViewBranch: { + // Typical Windows style listview branch element (dotted line). + + // Create the dotline pixmaps if not already created + if ( !d->verticalLine ) + { + // make 128*1 and 1*128 bitmaps that can be used for + // drawing the right sort of lines. + d->verticalLine = new QBitmap( 1, 129, true ); + d->horizontalLine = new QBitmap( 128, 1, true ); + QPointArray a( 64 ); + QPainter p2; + p2.begin( d->verticalLine ); + + int i; + for( i=0; i < 64; i++ ) + a.setPoint( i, 0, i*2+1 ); + p2.setPen( color1 ); + p2.drawPoints( a ); + p2.end(); + QApplication::flushX(); + d->verticalLine->setMask( *d->verticalLine ); + + p2.begin( d->horizontalLine ); + for( i=0; i < 64; i++ ) + a.setPoint( i, i*2+1, 0 ); + p2.setPen( color1 ); + p2.drawPoints( a ); + p2.end(); + QApplication::flushX(); + d->horizontalLine->setMask( *d->horizontalLine ); + } + + p->setPen( cg.text() ); // cg.dark() is bad for dark color schemes. + + if (flags & Style_Horizontal) + { + int point = r.x(); + int other = r.y(); + int end = r.x()+r.width(); + int thickness = r.height(); + + while( point < end ) + { + int i = 128; + if ( i+point > end ) + i = end-point; + p->drawPixmap( point, other, *d->horizontalLine, 0, 0, i, thickness ); + point += i; + } + + } else { + int point = r.y(); + int other = r.x(); + int end = r.y()+r.height(); + int thickness = r.width(); + int pixmapoffset = (flags & Style_NoChange) ? 0 : 1; // ### Hackish + + while( point < end ) + { + int i = 128; + if ( i+point > end ) + i = end-point; + p->drawPixmap( other, point, *d->verticalLine, 0, pixmapoffset, thickness, i ); + point += i; + } + } + + break; + } + + // Reimplement the other primitives in your styles. + // The current implementation just paints something visibly different. + case KPE_ToolBarHandle: + case KPE_GeneralHandle: + case KPE_SliderHandle: + p->fillRect(r, cg.light()); + break; + + case KPE_SliderGroove: + p->fillRect(r, cg.dark()); + break; + + default: + p->fillRect(r, Qt::yellow); // Something really bad happened - highlight. + break; + } +} + + +int KStyle::kPixelMetric( KStylePixelMetric kpm, const QWidget* /* widget */) const +{ + int value; + switch(kpm) + { + case KPM_ListViewBranchThickness: + value = 1; + break; + + case KPM_MenuItemSeparatorHeight: + case KPM_MenuItemHMargin: + case KPM_MenuItemVMargin: + case KPM_MenuItemHFrame: + case KPM_MenuItemVFrame: + case KPM_MenuItemCheckMarkHMargin: + case KPM_MenuItemArrowHMargin: + case KPM_MenuItemTabSpacing: + default: + value = 0; + } + + return value; +} + + +// ----------------------------------------------------------------------------- + +void KStyle::drawPrimitive( PrimitiveElement pe, + QPainter* p, + const QRect &r, + const QColorGroup &cg, + SFlags flags, + const QStyleOption& opt ) const +{ + // TOOLBAR/DOCK WINDOW HANDLE + // ------------------------------------------------------------------------ + if (pe == PE_DockWindowHandle) + { + // Wild workarounds are here. Beware. + QWidget *widget, *parent; + + if (p && p->device()->devType() == QInternal::Widget) { + widget = static_cast<QWidget*>(p->device()); + parent = widget->parentWidget(); + } else + return; // Don't paint on non-widgets + + // Check if we are a normal toolbar or a hidden dockwidget. + if ( parent && + (parent->inherits("QToolBar") || // Normal toolbar + (parent->inherits("QMainWindow")) )) // Collapsed dock + + // Draw a toolbar handle + drawKStylePrimitive( KPE_ToolBarHandle, p, widget, r, cg, flags, opt ); + + else if ( widget->inherits("QDockWindowHandle") ) + + // Draw a dock window handle + drawKStylePrimitive( KPE_DockWindowHandle, p, widget, r, cg, flags, opt ); + + else + // General handle, probably a kicker applet handle. + drawKStylePrimitive( KPE_GeneralHandle, p, widget, r, cg, flags, opt ); + + } else + QCommonStyle::drawPrimitive( pe, p, r, cg, flags, opt ); +} + + + +void KStyle::drawControl( ControlElement element, + QPainter* p, + const QWidget* widget, + const QRect &r, + const QColorGroup &cg, + SFlags flags, + const QStyleOption &opt ) const +{ + switch (element) + { + // TABS + // ------------------------------------------------------------------------ + case CE_TabBarTab: { + const QTabBar* tb = (const QTabBar*) widget; + QTabBar::Shape tbs = tb->shape(); + bool selected = flags & Style_Selected; + int x = r.x(), y=r.y(), bottom=r.bottom(), right=r.right(); + + switch (tbs) { + + case QTabBar::RoundedAbove: { + if (!selected) + p->translate(0,1); + p->setPen(selected ? cg.light() : cg.shadow()); + p->drawLine(x, y+4, x, bottom); + p->drawLine(x, y+4, x+4, y); + p->drawLine(x+4, y, right-1, y); + if (selected) + p->setPen(cg.shadow()); + p->drawLine(right, y+1, right, bottom); + + p->setPen(cg.midlight()); + p->drawLine(x+1, y+4, x+1, bottom); + p->drawLine(x+1, y+4, x+4, y+1); + p->drawLine(x+5, y+1, right-2, y+1); + + if (selected) { + p->setPen(cg.mid()); + p->drawLine(right-1, y+1, right-1, bottom); + } else { + p->setPen(cg.mid()); + p->drawPoint(right-1, y+1); + p->drawLine(x+4, y+2, right-1, y+2); + p->drawLine(x+3, y+3, right-1, y+3); + p->fillRect(x+2, y+4, r.width()-3, r.height()-6, cg.mid()); + + p->setPen(cg.light()); + p->drawLine(x, bottom-1, right, bottom-1); + p->translate(0,-1); + } + break; + } + + case QTabBar::RoundedBelow: { + if (!selected) + p->translate(0,-1); + p->setPen(selected ? cg.light() : cg.shadow()); + p->drawLine(x, bottom-4, x, y); + if (selected) + p->setPen(cg.mid()); + p->drawLine(x, bottom-4, x+4, bottom); + if (selected) + p->setPen(cg.shadow()); + p->drawLine(x+4, bottom, right-1, bottom); + p->drawLine(right, bottom-1, right, y); + + p->setPen(cg.midlight()); + p->drawLine(x+1, bottom-4, x+1, y); + p->drawLine(x+1, bottom-4, x+4, bottom-1); + p->drawLine(x+5, bottom-1, right-2, bottom-1); + + if (selected) { + p->setPen(cg.mid()); + p->drawLine(right-1, y, right-1, bottom-1); + } else { + p->setPen(cg.mid()); + p->drawPoint(right-1, bottom-1); + p->drawLine(x+4, bottom-2, right-1, bottom-2); + p->drawLine(x+3, bottom-3, right-1, bottom-3); + p->fillRect(x+2, y+2, r.width()-3, r.height()-6, cg.mid()); + p->translate(0,1); + p->setPen(cg.dark()); + p->drawLine(x, y, right, y); + } + break; + } + + case QTabBar::TriangularAbove: { + if (!selected) + p->translate(0,1); + p->setPen(selected ? cg.light() : cg.shadow()); + p->drawLine(x, bottom, x, y+6); + p->drawLine(x, y+6, x+6, y); + p->drawLine(x+6, y, right-6, y); + if (selected) + p->setPen(cg.mid()); + p->drawLine(right-5, y+1, right-1, y+5); + p->setPen(cg.shadow()); + p->drawLine(right, y+6, right, bottom); + + p->setPen(cg.midlight()); + p->drawLine(x+1, bottom, x+1, y+6); + p->drawLine(x+1, y+6, x+6, y+1); + p->drawLine(x+6, y+1, right-6, y+1); + p->drawLine(right-5, y+2, right-2, y+5); + p->setPen(cg.mid()); + p->drawLine(right-1, y+6, right-1, bottom); + + QPointArray a(6); + a.setPoint(0, x+2, bottom); + a.setPoint(1, x+2, y+7); + a.setPoint(2, x+7, y+2); + a.setPoint(3, right-7, y+2); + a.setPoint(4, right-2, y+7); + a.setPoint(5, right-2, bottom); + p->setPen (selected ? cg.background() : cg.mid()); + p->setBrush(selected ? cg.background() : cg.mid()); + p->drawPolygon(a); + p->setBrush(NoBrush); + if (!selected) { + p->translate(0,-1); + p->setPen(cg.light()); + p->drawLine(x, bottom, right, bottom); + } + break; + } + + default: { // QTabBar::TriangularBelow + if (!selected) + p->translate(0,-1); + p->setPen(selected ? cg.light() : cg.shadow()); + p->drawLine(x, y, x, bottom-6); + if (selected) + p->setPen(cg.mid()); + p->drawLine(x, bottom-6, x+6, bottom); + if (selected) + p->setPen(cg.shadow()); + p->drawLine(x+6, bottom, right-6, bottom); + p->drawLine(right-5, bottom-1, right-1, bottom-5); + if (!selected) + p->setPen(cg.shadow()); + p->drawLine(right, bottom-6, right, y); + + p->setPen(cg.midlight()); + p->drawLine(x+1, y, x+1, bottom-6); + p->drawLine(x+1, bottom-6, x+6, bottom-1); + p->drawLine(x+6, bottom-1, right-6, bottom-1); + p->drawLine(right-5, bottom-2, right-2, bottom-5); + p->setPen(cg.mid()); + p->drawLine(right-1, bottom-6, right-1, y); + + QPointArray a(6); + a.setPoint(0, x+2, y); + a.setPoint(1, x+2, bottom-7); + a.setPoint(2, x+7, bottom-2); + a.setPoint(3, right-7, bottom-2); + a.setPoint(4, right-2, bottom-7); + a.setPoint(5, right-2, y); + p->setPen (selected ? cg.background() : cg.mid()); + p->setBrush(selected ? cg.background() : cg.mid()); + p->drawPolygon(a); + p->setBrush(NoBrush); + if (!selected) { + p->translate(0,1); + p->setPen(cg.dark()); + p->drawLine(x, y, right, y); + } + break; + } + }; + + break; + } + + // Popup menu scroller + // ------------------------------------------------------------------------ + case CE_PopupMenuScroller: { + p->fillRect(r, cg.background()); + drawPrimitive(PE_ButtonTool, p, r, cg, Style_Enabled); + drawPrimitive((flags & Style_Up) ? PE_ArrowUp : PE_ArrowDown, p, r, cg, Style_Enabled); + break; + } + + + // PROGRESSBAR + // ------------------------------------------------------------------------ + case CE_ProgressBarGroove: { + QRect fr = subRect(SR_ProgressBarGroove, widget); + drawPrimitive(PE_Panel, p, fr, cg, Style_Sunken, QStyleOption::Default); + break; + } + + case CE_ProgressBarContents: { + // ### Take into account totalSteps() for busy indicator + const QProgressBar* pb = (const QProgressBar*)widget; + QRect cr = subRect(SR_ProgressBarContents, widget); + double progress = pb->progress(); + bool reverse = QApplication::reverseLayout(); + int steps = pb->totalSteps(); + + if (!cr.isValid()) + return; + + // Draw progress bar + if (progress > 0 || steps == 0) { + double pg = (steps == 0) ? 0.1 : progress / steps; + int width = QMIN(cr.width(), (int)(pg * cr.width())); + if (steps == 0) { //Busy indicator + + if (width < 1) width = 1; //A busy indicator with width 0 is kind of useless + + int remWidth = cr.width() - width; //Never disappear completely + if (remWidth <= 0) remWidth = 1; //Do something non-crashy when too small... + + int pstep = int(progress) % ( 2 * remWidth ); + + if ( pstep > remWidth ) { + //Bounce about.. We're remWidth + some delta, we want to be remWidth - delta... + // - ( (remWidth + some delta) - 2* remWidth ) = - (some deleta - remWidth) = remWidth - some delta.. + pstep = - (pstep - 2 * remWidth ); + } + + if (reverse) + p->fillRect(cr.x() + cr.width() - width - pstep, cr.y(), width, cr.height(), + cg.brush(QColorGroup::Highlight)); + else + p->fillRect(cr.x() + pstep, cr.y(), width, cr.height(), + cg.brush(QColorGroup::Highlight)); + + return; + } + + + // Do fancy gradient for highcolor displays + if (d->highcolor) { + QColor c(cg.highlight()); + KPixmap pix; + pix.resize(cr.width(), cr.height()); + KPixmapEffect::gradient(pix, reverse ? c.light(150) : c.dark(150), + reverse ? c.dark(150) : c.light(150), + KPixmapEffect::HorizontalGradient); + if (reverse) + p->drawPixmap(cr.x()+(cr.width()-width), cr.y(), pix, + cr.width()-width, 0, width, cr.height()); + else + p->drawPixmap(cr.x(), cr.y(), pix, 0, 0, width, cr.height()); + } else + if (reverse) + p->fillRect(cr.x()+(cr.width()-width), cr.y(), width, cr.height(), + cg.brush(QColorGroup::Highlight)); + else + p->fillRect(cr.x(), cr.y(), width, cr.height(), + cg.brush(QColorGroup::Highlight)); + } + break; + } + + case CE_ProgressBarLabel: { + const QProgressBar* pb = (const QProgressBar*)widget; + QRect cr = subRect(SR_ProgressBarContents, widget); + double progress = pb->progress(); + bool reverse = QApplication::reverseLayout(); + int steps = pb->totalSteps(); + + if (!cr.isValid()) + return; + + QFont font = p->font(); + font.setBold(true); + p->setFont(font); + + // Draw label + if (progress > 0 || steps == 0) { + double pg = (steps == 0) ? 1.0 : progress / steps; + int width = QMIN(cr.width(), (int)(pg * cr.width())); + QRect crect; + if (reverse) + crect.setRect(cr.x()+(cr.width()-width), cr.y(), cr.width(), cr.height()); + else + crect.setRect(cr.x()+width, cr.y(), cr.width(), cr.height()); + + p->save(); + p->setPen(pb->isEnabled() ? (reverse ? cg.text() : cg.highlightedText()) : cg.text()); + p->drawText(r, AlignCenter, pb->progressString()); + p->setClipRect(crect); + p->setPen(reverse ? cg.highlightedText() : cg.text()); + p->drawText(r, AlignCenter, pb->progressString()); + p->restore(); + + } else { + p->setPen(cg.text()); + p->drawText(r, AlignCenter, pb->progressString()); + } + + break; + } + + default: + QCommonStyle::drawControl(element, p, widget, r, cg, flags, opt); + } +} + + +QRect KStyle::subRect(SubRect r, const QWidget* widget) const +{ + switch(r) + { + // KDE2 look smooth progress bar + // ------------------------------------------------------------------------ + case SR_ProgressBarGroove: + return widget->rect(); + + case SR_ProgressBarContents: + case SR_ProgressBarLabel: { + // ### take into account indicatorFollowsStyle() + QRect rt = widget->rect(); + return QRect(rt.x()+2, rt.y()+2, rt.width()-4, rt.height()-4); + } + + default: + return QCommonStyle::subRect(r, widget); + } +} + + +int KStyle::pixelMetric(PixelMetric m, const QWidget* widget) const +{ + switch(m) + { + // BUTTONS + // ------------------------------------------------------------------------ + case PM_ButtonShiftHorizontal: // Offset by 1 + case PM_ButtonShiftVertical: // ### Make configurable + return 1; + + case PM_DockWindowHandleExtent: + { + QWidget* parent = 0; + // Check that we are not a normal toolbar or a hidden dockwidget, + // in which case we need to adjust the height for font size + if (widget && (parent = widget->parentWidget() ) + && !parent->inherits("QToolBar") + && !parent->inherits("QMainWindow") + && widget->inherits("QDockWindowHandle") ) + return widget->fontMetrics().lineSpacing(); + else + return QCommonStyle::pixelMetric(m, widget); + } + + // TABS + // ------------------------------------------------------------------------ + case PM_TabBarTabHSpace: + return 24; + + case PM_TabBarTabVSpace: { + const QTabBar * tb = (const QTabBar *) widget; + if ( tb->shape() == QTabBar::RoundedAbove || + tb->shape() == QTabBar::RoundedBelow ) + return 10; + else + return 4; + } + + case PM_TabBarTabOverlap: { + const QTabBar* tb = (const QTabBar*)widget; + QTabBar::Shape tbs = tb->shape(); + + if ( (tbs == QTabBar::RoundedAbove) || + (tbs == QTabBar::RoundedBelow) ) + return 0; + else + return 2; + } + + // SLIDER + // ------------------------------------------------------------------------ + case PM_SliderLength: + return 18; + + case PM_SliderThickness: + return 24; + + // Determines how much space to leave for the actual non-tickmark + // portion of the slider. + case PM_SliderControlThickness: { + const QSlider* slider = (const QSlider*)widget; + QSlider::TickSetting ts = slider->tickmarks(); + int thickness = (slider->orientation() == Horizontal) ? + slider->height() : slider->width(); + switch (ts) { + case QSlider::NoMarks: // Use total area. + break; + case QSlider::Both: + thickness = (thickness/2) + 3; // Use approx. 1/2 of area. + break; + default: // Use approx. 2/3 of area + thickness = ((thickness*2)/3) + 3; + break; + }; + return thickness; + } + + // SPLITTER + // ------------------------------------------------------------------------ + case PM_SplitterWidth: + if (widget && widget->inherits("QDockWindowResizeHandle")) + return 8; // ### why do we need 2pix extra? + else + return 6; + + // FRAMES + // ------------------------------------------------------------------------ + case PM_MenuBarFrameWidth: + return 1; + + case PM_DockWindowFrameWidth: + return 1; + + // GENERAL + // ------------------------------------------------------------------------ + case PM_MaximumDragDistance: + return -1; + + case PM_MenuBarItemSpacing: + return 5; + + case PM_ToolBarItemSpacing: + return 0; + + case PM_PopupMenuScrollerHeight: + return pixelMetric( PM_ScrollBarExtent, 0); + + default: + return QCommonStyle::pixelMetric( m, widget ); + } +} + +//Helper to find the next sibling that's not hidden +static QListViewItem* nextVisibleSibling(QListViewItem* item) +{ + QListViewItem* sibling = item; + do + { + sibling = sibling->nextSibling(); + } + while (sibling && !sibling->isVisible()); + + return sibling; +} + +void KStyle::drawComplexControl( ComplexControl control, + QPainter* p, + const QWidget* widget, + const QRect &r, + const QColorGroup &cg, + SFlags flags, + SCFlags controls, + SCFlags active, + const QStyleOption &opt ) const +{ + switch(control) + { + // 3 BUTTON SCROLLBAR + // ------------------------------------------------------------------------ + case CC_ScrollBar: { + // Many thanks to Brad Hughes for contributing this code. + bool useThreeButtonScrollBar = (d->scrollbarType & ThreeButtonScrollBar); + + const QScrollBar *sb = (const QScrollBar*)widget; + bool maxedOut = (sb->minValue() == sb->maxValue()); + bool horizontal = (sb->orientation() == Qt::Horizontal); + SFlags sflags = ((horizontal ? Style_Horizontal : Style_Default) | + (maxedOut ? Style_Default : Style_Enabled)); + + QRect addline, subline, subline2, addpage, subpage, slider, first, last; + subline = querySubControlMetrics(control, widget, SC_ScrollBarSubLine, opt); + addline = querySubControlMetrics(control, widget, SC_ScrollBarAddLine, opt); + subpage = querySubControlMetrics(control, widget, SC_ScrollBarSubPage, opt); + addpage = querySubControlMetrics(control, widget, SC_ScrollBarAddPage, opt); + slider = querySubControlMetrics(control, widget, SC_ScrollBarSlider, opt); + first = querySubControlMetrics(control, widget, SC_ScrollBarFirst, opt); + last = querySubControlMetrics(control, widget, SC_ScrollBarLast, opt); + subline2 = addline; + + if ( useThreeButtonScrollBar ) + if (horizontal) + subline2.moveBy(-addline.width(), 0); + else + subline2.moveBy(0, -addline.height()); + + // Draw the up/left button set + if ((controls & SC_ScrollBarSubLine) && subline.isValid()) { + drawPrimitive(PE_ScrollBarSubLine, p, subline, cg, + sflags | (active == SC_ScrollBarSubLine ? + Style_Down : Style_Default)); + + if (useThreeButtonScrollBar && subline2.isValid()) + drawPrimitive(PE_ScrollBarSubLine, p, subline2, cg, + sflags | (active == SC_ScrollBarSubLine ? + Style_Down : Style_Default)); + } + + if ((controls & SC_ScrollBarAddLine) && addline.isValid()) + drawPrimitive(PE_ScrollBarAddLine, p, addline, cg, + sflags | ((active == SC_ScrollBarAddLine) ? + Style_Down : Style_Default)); + + if ((controls & SC_ScrollBarSubPage) && subpage.isValid()) + drawPrimitive(PE_ScrollBarSubPage, p, subpage, cg, + sflags | ((active == SC_ScrollBarSubPage) ? + Style_Down : Style_Default)); + + if ((controls & SC_ScrollBarAddPage) && addpage.isValid()) + drawPrimitive(PE_ScrollBarAddPage, p, addpage, cg, + sflags | ((active == SC_ScrollBarAddPage) ? + Style_Down : Style_Default)); + + if ((controls & SC_ScrollBarFirst) && first.isValid()) + drawPrimitive(PE_ScrollBarFirst, p, first, cg, + sflags | ((active == SC_ScrollBarFirst) ? + Style_Down : Style_Default)); + + if ((controls & SC_ScrollBarLast) && last.isValid()) + drawPrimitive(PE_ScrollBarLast, p, last, cg, + sflags | ((active == SC_ScrollBarLast) ? + Style_Down : Style_Default)); + + if ((controls & SC_ScrollBarSlider) && slider.isValid()) { + drawPrimitive(PE_ScrollBarSlider, p, slider, cg, + sflags | ((active == SC_ScrollBarSlider) ? + Style_Down : Style_Default)); + // Draw focus rect + if (sb->hasFocus()) { + QRect fr(slider.x() + 2, slider.y() + 2, + slider.width() - 5, slider.height() - 5); + drawPrimitive(PE_FocusRect, p, fr, cg, Style_Default); + } + } + break; + } + + + // SLIDER + // ------------------------------------------------------------------- + case CC_Slider: { + const QSlider* slider = (const QSlider*)widget; + QRect groove = querySubControlMetrics(CC_Slider, widget, SC_SliderGroove, opt); + QRect handle = querySubControlMetrics(CC_Slider, widget, SC_SliderHandle, opt); + + // Double-buffer slider for no flicker + QPixmap pix(widget->size()); + QPainter p2; + p2.begin(&pix); + + if ( slider->parentWidget() && + slider->parentWidget()->backgroundPixmap() && + !slider->parentWidget()->backgroundPixmap()->isNull() ) { + QPixmap pixmap = *(slider->parentWidget()->backgroundPixmap()); + p2.drawTiledPixmap(r, pixmap, slider->pos()); + } else + pix.fill(cg.background()); + + // Draw slider groove + if ((controls & SC_SliderGroove) && groove.isValid()) { + drawKStylePrimitive( KPE_SliderGroove, &p2, widget, groove, cg, flags, opt ); + + // Draw the focus rect around the groove + if (slider->hasFocus()) + drawPrimitive(PE_FocusRect, &p2, groove, cg); + } + + // Draw the tickmarks + if (controls & SC_SliderTickmarks) + QCommonStyle::drawComplexControl(control, &p2, widget, + r, cg, flags, SC_SliderTickmarks, active, opt); + + // Draw the slider handle + if ((controls & SC_SliderHandle) && handle.isValid()) { + if (active == SC_SliderHandle) + flags |= Style_Active; + drawKStylePrimitive( KPE_SliderHandle, &p2, widget, handle, cg, flags, opt ); + } + + p2.end(); + bitBlt((QWidget*)widget, r.x(), r.y(), &pix); + break; + } + + // LISTVIEW + // ------------------------------------------------------------------- + case CC_ListView: { + + /* + * Many thanks to TrollTech AS for donating CC_ListView from QWindowsStyle. + * CC_ListView code is Copyright (C) 1998-2000 TrollTech AS. + */ + + // Paint the icon and text. + if ( controls & SC_ListView ) + QCommonStyle::drawComplexControl( control, p, widget, r, cg, flags, controls, active, opt ); + + // If we're have a branch or are expanded... + if ( controls & (SC_ListViewBranch | SC_ListViewExpand) ) + { + // If no list view item was supplied, break + if (opt.isDefault()) + break; + + QListViewItem *item = opt.listViewItem(); + QListViewItem *child = item->firstChild(); + + int y = r.y(); + int c; // dotline vertice count + int dotoffset = 0; + QPointArray dotlines; + + if ( active == SC_All && controls == SC_ListViewExpand ) { + // We only need to draw a vertical line + c = 2; + dotlines.resize(2); + dotlines[0] = QPoint( r.right(), r.top() ); + dotlines[1] = QPoint( r.right(), r.bottom() ); + + } else { + + int linetop = 0, linebot = 0; + // each branch needs at most two lines, ie. four end points + dotoffset = (item->itemPos() + item->height() - y) % 2; + dotlines.resize( item->childCount() * 4 ); + c = 0; + + // skip the stuff above the exposed rectangle + while ( child && y + child->height() <= 0 ) + { + y += child->totalHeight(); + child = nextVisibleSibling(child); + } + + int bx = r.width() / 2; + + // paint stuff in the magical area + QListView* v = item->listView(); + int lh = QMAX( p->fontMetrics().height() + 2 * v->itemMargin(), + QApplication::globalStrut().height() ); + if ( lh % 2 > 0 ) + lh++; + + // Draw all the expand/close boxes... + QRect boxrect; + QStyle::StyleFlags boxflags; + while ( child && y < r.height() ) + { + linebot = y + lh/2; + if ( (child->isExpandable() || child->childCount()) && + (child->height() > 0) ) + { + // The primitive requires a rect. + boxrect = QRect( bx-4, linebot-4, 9, 9 ); + boxflags = child->isOpen() ? QStyle::Style_Off : QStyle::Style_On; + + // KStyle extension: Draw the box and expand/collapse indicator + drawKStylePrimitive( KPE_ListViewExpander, p, NULL, boxrect, cg, boxflags, opt ); + + // dotlinery + p->setPen( cg.mid() ); + dotlines[c++] = QPoint( bx, linetop ); + dotlines[c++] = QPoint( bx, linebot - 5 ); + dotlines[c++] = QPoint( bx + 5, linebot ); + dotlines[c++] = QPoint( r.width(), linebot ); + linetop = linebot + 5; + } else { + // just dotlinery + dotlines[c++] = QPoint( bx+1, linebot ); + dotlines[c++] = QPoint( r.width(), linebot ); + } + + y += child->totalHeight(); + child = nextVisibleSibling(child); + } + + if ( child ) // there's a child to draw, so move linebot to edge of rectangle + linebot = r.height(); + + if ( linetop < linebot ) + { + dotlines[c++] = QPoint( bx, linetop ); + dotlines[c++] = QPoint( bx, linebot ); + } + } + + // Draw all the branches... + static int thickness = kPixelMetric( KPM_ListViewBranchThickness ); + int line; // index into dotlines + QRect branchrect; + QStyle::StyleFlags branchflags; + for( line = 0; line < c; line += 2 ) + { + // assumptions here: lines are horizontal or vertical. + // lines always start with the numerically lowest + // coordinate. + + // point ... relevant coordinate of current point + // end ..... same coordinate of the end of the current line + // other ... the other coordinate of the current point/line + if ( dotlines[line].y() == dotlines[line+1].y() ) + { + // Horizontal branch + int end = dotlines[line+1].x(); + int point = dotlines[line].x(); + int other = dotlines[line].y(); + + branchrect = QRect( point, other-(thickness/2), end-point, thickness ); + branchflags = QStyle::Style_Horizontal; + + // KStyle extension: Draw the horizontal branch + drawKStylePrimitive( KPE_ListViewBranch, p, NULL, branchrect, cg, branchflags, opt ); + + } else { + // Vertical branch + int end = dotlines[line+1].y(); + int point = dotlines[line].y(); + int other = dotlines[line].x(); + int pixmapoffset = ((point & 1) != dotoffset ) ? 1 : 0; + + branchrect = QRect( other-(thickness/2), point, thickness, end-point ); + if (!pixmapoffset) // ### Hackish - used to hint the offset + branchflags = QStyle::Style_NoChange; + else + branchflags = QStyle::Style_Default; + + // KStyle extension: Draw the vertical branch + drawKStylePrimitive( KPE_ListViewBranch, p, NULL, branchrect, cg, branchflags, opt ); + } + } + } + break; + } + + default: + QCommonStyle::drawComplexControl( control, p, widget, r, cg, + flags, controls, active, opt ); + break; + } +} + + +QStyle::SubControl KStyle::querySubControl( ComplexControl control, + const QWidget* widget, + const QPoint &pos, + const QStyleOption &opt ) const +{ + QStyle::SubControl ret = QCommonStyle::querySubControl(control, widget, pos, opt); + + if (d->scrollbarType == ThreeButtonScrollBar) { + // Enable third button + if (control == CC_ScrollBar && ret == SC_None) + ret = SC_ScrollBarSubLine; + } + return ret; +} + + +QRect KStyle::querySubControlMetrics( ComplexControl control, + const QWidget* widget, + SubControl sc, + const QStyleOption &opt ) const +{ + QRect ret; + + if (control == CC_ScrollBar) + { + bool threeButtonScrollBar = d->scrollbarType & ThreeButtonScrollBar; + bool platinumScrollBar = d->scrollbarType & PlatinumStyleScrollBar; + bool nextScrollBar = d->scrollbarType & NextStyleScrollBar; + + const QScrollBar *sb = (const QScrollBar*)widget; + bool horizontal = sb->orientation() == Qt::Horizontal; + int sliderstart = sb->sliderStart(); + int sbextent = pixelMetric(PM_ScrollBarExtent, widget); + int maxlen = (horizontal ? sb->width() : sb->height()) + - (sbextent * (threeButtonScrollBar ? 3 : 2)); + int sliderlen; + + // calculate slider length + if (sb->maxValue() != sb->minValue()) + { + uint range = sb->maxValue() - sb->minValue(); + sliderlen = (sb->pageStep() * maxlen) / (range + sb->pageStep()); + + int slidermin = pixelMetric( PM_ScrollBarSliderMin, widget ); + if ( sliderlen < slidermin || range > INT_MAX / 2 ) + sliderlen = slidermin; + if ( sliderlen > maxlen ) + sliderlen = maxlen; + } else + sliderlen = maxlen; + + // Subcontrols + switch (sc) + { + case SC_ScrollBarSubLine: { + // top/left button + if (platinumScrollBar) { + if (horizontal) + ret.setRect(sb->width() - 2 * sbextent, 0, sbextent, sbextent); + else + ret.setRect(0, sb->height() - 2 * sbextent, sbextent, sbextent); + } else + ret.setRect(0, 0, sbextent, sbextent); + break; + } + + case SC_ScrollBarAddLine: { + // bottom/right button + if (nextScrollBar) { + if (horizontal) + ret.setRect(sbextent, 0, sbextent, sbextent); + else + ret.setRect(0, sbextent, sbextent, sbextent); + } else { + if (horizontal) + ret.setRect(sb->width() - sbextent, 0, sbextent, sbextent); + else + ret.setRect(0, sb->height() - sbextent, sbextent, sbextent); + } + break; + } + + case SC_ScrollBarSubPage: { + // between top/left button and slider + if (platinumScrollBar) { + if (horizontal) + ret.setRect(0, 0, sliderstart, sbextent); + else + ret.setRect(0, 0, sbextent, sliderstart); + } else if (nextScrollBar) { + if (horizontal) + ret.setRect(sbextent*2, 0, sliderstart-2*sbextent, sbextent); + else + ret.setRect(0, sbextent*2, sbextent, sliderstart-2*sbextent); + } else { + if (horizontal) + ret.setRect(sbextent, 0, sliderstart - sbextent, sbextent); + else + ret.setRect(0, sbextent, sbextent, sliderstart - sbextent); + } + break; + } + + case SC_ScrollBarAddPage: { + // between bottom/right button and slider + int fudge; + + if (platinumScrollBar) + fudge = 0; + else if (nextScrollBar) + fudge = 2*sbextent; + else + fudge = sbextent; + + if (horizontal) + ret.setRect(sliderstart + sliderlen, 0, + maxlen - sliderstart - sliderlen + fudge, sbextent); + else + ret.setRect(0, sliderstart + sliderlen, sbextent, + maxlen - sliderstart - sliderlen + fudge); + break; + } + + case SC_ScrollBarGroove: { + int multi = threeButtonScrollBar ? 3 : 2; + int fudge; + + if (platinumScrollBar) + fudge = 0; + else if (nextScrollBar) + fudge = 2*sbextent; + else + fudge = sbextent; + + if (horizontal) + ret.setRect(fudge, 0, sb->width() - sbextent * multi, sb->height()); + else + ret.setRect(0, fudge, sb->width(), sb->height() - sbextent * multi); + break; + } + + case SC_ScrollBarSlider: { + if (horizontal) + ret.setRect(sliderstart, 0, sliderlen, sbextent); + else + ret.setRect(0, sliderstart, sbextent, sliderlen); + break; + } + + default: + ret = QCommonStyle::querySubControlMetrics(control, widget, sc, opt); + break; + } + } else + ret = QCommonStyle::querySubControlMetrics(control, widget, sc, opt); + + return ret; +} + +static const char * const kstyle_close_xpm[] = { +"12 12 2 1", +"# c #000000", +". c None", +"............", +"............", +"..##....##..", +"...##..##...", +"....####....", +".....##.....", +"....####....", +"...##..##...", +"..##....##..", +"............", +"............", +"............"}; + +static const char * const kstyle_maximize_xpm[]={ +"12 12 2 1", +"# c #000000", +". c None", +"............", +"............", +".##########.", +".##########.", +".#........#.", +".#........#.", +".#........#.", +".#........#.", +".#........#.", +".#........#.", +".##########.", +"............"}; + + +static const char * const kstyle_minimize_xpm[] = { +"12 12 2 1", +"# c #000000", +". c None", +"............", +"............", +"............", +"............", +"............", +"............", +"............", +"...######...", +"...######...", +"............", +"............", +"............"}; + +static const char * const kstyle_normalizeup_xpm[] = { +"12 12 2 1", +"# c #000000", +". c None", +"............", +"...#######..", +"...#######..", +"...#.....#..", +".#######.#..", +".#######.#..", +".#.....#.#..", +".#.....###..", +".#.....#....", +".#.....#....", +".#######....", +"............"}; + + +static const char * const kstyle_shade_xpm[] = { +"12 12 2 1", +"# c #000000", +". c None", +"............", +"............", +"............", +"............", +"............", +".....#......", +"....###.....", +"...#####....", +"..#######...", +"............", +"............", +"............"}; + +static const char * const kstyle_unshade_xpm[] = { +"12 12 2 1", +"# c #000000", +". c None", +"............", +"............", +"............", +"............", +"..#######...", +"...#####....", +"....###.....", +".....#......", +"............", +"............", +"............", +"............"}; + +static const char * const dock_window_close_xpm[] = { +"8 8 2 1", +"# c #000000", +". c None", +"##....##", +".##..##.", +"..####..", +"...##...", +"..####..", +".##..##.", +"##....##", +"........"}; + +// Message box icons, from page 210 of the Windows style guide. + +// Hand-drawn to resemble Microsoft's icons, but in the Mac/Netscape +// palette. The "question mark" icon, which Microsoft recommends not +// using but a lot of people still use, is left out. + +/* XPM */ +static const char * const information_xpm[]={ +"32 32 5 1", +". c None", +"c c #000000", +"* c #999999", +"a c #ffffff", +"b c #0000ff", +"...........********.............", +"........***aaaaaaaa***..........", +"......**aaaaaaaaaaaaaa**........", +".....*aaaaaaaaaaaaaaaaaa*.......", +"....*aaaaaaaabbbbaaaaaaaac......", +"...*aaaaaaaabbbbbbaaaaaaaac.....", +"..*aaaaaaaaabbbbbbaaaaaaaaac....", +".*aaaaaaaaaaabbbbaaaaaaaaaaac...", +".*aaaaaaaaaaaaaaaaaaaaaaaaaac*..", +"*aaaaaaaaaaaaaaaaaaaaaaaaaaaac*.", +"*aaaaaaaaaabbbbbbbaaaaaaaaaaac*.", +"*aaaaaaaaaaaabbbbbaaaaaaaaaaac**", +"*aaaaaaaaaaaabbbbbaaaaaaaaaaac**", +"*aaaaaaaaaaaabbbbbaaaaaaaaaaac**", +"*aaaaaaaaaaaabbbbbaaaaaaaaaaac**", +"*aaaaaaaaaaaabbbbbaaaaaaaaaaac**", +".*aaaaaaaaaaabbbbbaaaaaaaaaac***", +".*aaaaaaaaaaabbbbbaaaaaaaaaac***", +"..*aaaaaaaaaabbbbbaaaaaaaaac***.", +"...caaaaaaabbbbbbbbbaaaaaac****.", +"....caaaaaaaaaaaaaaaaaaaac****..", +".....caaaaaaaaaaaaaaaaaac****...", +"......ccaaaaaaaaaaaaaacc****....", +".......*cccaaaaaaaaccc*****.....", +"........***cccaaaac*******......", +"..........****caaac*****........", +".............*caaac**...........", +"...............caac**...........", +"................cac**...........", +".................cc**...........", +"..................***...........", +"...................**..........."}; +/* XPM */ +static const char* const warning_xpm[]={ +"32 32 4 1", +". c None", +"a c #ffff00", +"* c #000000", +"b c #999999", +".............***................", +"............*aaa*...............", +"...........*aaaaa*b.............", +"...........*aaaaa*bb............", +"..........*aaaaaaa*bb...........", +"..........*aaaaaaa*bb...........", +".........*aaaaaaaaa*bb..........", +".........*aaaaaaaaa*bb..........", +"........*aaaaaaaaaaa*bb.........", +"........*aaaa***aaaa*bb.........", +".......*aaaa*****aaaa*bb........", +".......*aaaa*****aaaa*bb........", +"......*aaaaa*****aaaaa*bb.......", +"......*aaaaa*****aaaaa*bb.......", +".....*aaaaaa*****aaaaaa*bb......", +".....*aaaaaa*****aaaaaa*bb......", +"....*aaaaaaaa***aaaaaaaa*bb.....", +"....*aaaaaaaa***aaaaaaaa*bb.....", +"...*aaaaaaaaa***aaaaaaaaa*bb....", +"...*aaaaaaaaaa*aaaaaaaaaa*bb....", +"..*aaaaaaaaaaa*aaaaaaaaaaa*bb...", +"..*aaaaaaaaaaaaaaaaaaaaaaa*bb...", +".*aaaaaaaaaaaa**aaaaaaaaaaa*bb..", +".*aaaaaaaaaaa****aaaaaaaaaa*bb..", +"*aaaaaaaaaaaa****aaaaaaaaaaa*bb.", +"*aaaaaaaaaaaaa**aaaaaaaaaaaa*bb.", +"*aaaaaaaaaaaaaaaaaaaaaaaaaaa*bbb", +"*aaaaaaaaaaaaaaaaaaaaaaaaaaa*bbb", +".*aaaaaaaaaaaaaaaaaaaaaaaaa*bbbb", +"..*************************bbbbb", +"....bbbbbbbbbbbbbbbbbbbbbbbbbbb.", +".....bbbbbbbbbbbbbbbbbbbbbbbbb.."}; +/* XPM */ +static const char* const critical_xpm[]={ +"32 32 4 1", +". c None", +"a c #999999", +"* c #ff0000", +"b c #ffffff", +"...........********.............", +".........************...........", +".......****************.........", +"......******************........", +".....********************a......", +"....**********************a.....", +"...************************a....", +"..*******b**********b*******a...", +"..******bbb********bbb******a...", +".******bbbbb******bbbbb******a..", +".*******bbbbb****bbbbb*******a..", +"*********bbbbb**bbbbb*********a.", +"**********bbbbbbbbbb**********a.", +"***********bbbbbbbb***********aa", +"************bbbbbb************aa", +"************bbbbbb************aa", +"***********bbbbbbbb***********aa", +"**********bbbbbbbbbb**********aa", +"*********bbbbb**bbbbb*********aa", +".*******bbbbb****bbbbb*******aa.", +".******bbbbb******bbbbb******aa.", +"..******bbb********bbb******aaa.", +"..*******b**********b*******aa..", +"...************************aaa..", +"....**********************aaa...", +"....a********************aaa....", +".....a******************aaa.....", +"......a****************aaa......", +".......aa************aaaa.......", +".........aa********aaaaa........", +"...........aaaaaaaaaaa..........", +".............aaaaaaa............"}; + +QPixmap KStyle::stylePixmap( StylePixmap stylepixmap, + const QWidget* widget, + const QStyleOption& opt) const +{ + switch (stylepixmap) { + case SP_TitleBarShadeButton: + return QPixmap(const_cast<const char**>(kstyle_shade_xpm)); + case SP_TitleBarUnshadeButton: + return QPixmap(const_cast<const char**>(kstyle_unshade_xpm)); + case SP_TitleBarNormalButton: + return QPixmap(const_cast<const char**>(kstyle_normalizeup_xpm)); + case SP_TitleBarMinButton: + return QPixmap(const_cast<const char**>(kstyle_minimize_xpm)); + case SP_TitleBarMaxButton: + return QPixmap(const_cast<const char**>(kstyle_maximize_xpm)); + case SP_TitleBarCloseButton: + return QPixmap(const_cast<const char**>(kstyle_close_xpm)); + case SP_DockWindowCloseButton: + return QPixmap(const_cast<const char**>(dock_window_close_xpm )); + case SP_MessageBoxInformation: + return QPixmap(const_cast<const char**>(information_xpm)); + case SP_MessageBoxWarning: + return QPixmap(const_cast<const char**>(warning_xpm)); + case SP_MessageBoxCritical: + return QPixmap(const_cast<const char**>(critical_xpm)); + default: + break; + } + return QCommonStyle::stylePixmap(stylepixmap, widget, opt); +} + + +int KStyle::styleHint( StyleHint sh, const QWidget* w, + const QStyleOption &opt, QStyleHintReturn* shr) const +{ + switch (sh) + { + case SH_EtchDisabledText: + return d->etchDisabledText ? 1 : 0; + + case SH_PopupMenu_Scrollable: + return d->scrollablePopupmenus ? 1 : 0; + + case SH_MenuBar_AltKeyNavigation: + return d->menuAltKeyNavigation ? 1 : 0; + + case SH_PopupMenu_SubMenuPopupDelay: + if ( styleHint( SH_PopupMenu_SloppySubMenus, w ) ) + return QMIN( 100, d->popupMenuDelay ); + else + return d->popupMenuDelay; + + case SH_PopupMenu_SloppySubMenus: + return d->sloppySubMenus; + + case SH_ItemView_ChangeHighlightOnFocus: + case SH_Slider_SloppyKeyEvents: + case SH_MainWindow_SpaceBelowMenuBar: + case SH_PopupMenu_AllowActiveAndDisabled: + return 0; + + case SH_Slider_SnapToValue: + case SH_PrintDialog_RightAlignButtons: + case SH_FontDialog_SelectAssociatedText: + case SH_MenuBar_MouseTracking: + case SH_PopupMenu_MouseTracking: + case SH_ComboBox_ListMouseTracking: + case SH_ScrollBar_MiddleClickAbsolutePosition: + return 1; + case SH_LineEdit_PasswordCharacter: + { + if (w) { + const QFontMetrics &fm = w->fontMetrics(); + if (fm.inFont(QChar(0x25CF))) { + return 0x25CF; + } else if (fm.inFont(QChar(0x2022))) { + return 0x2022; + } + } + return '*'; + } + + default: + return QCommonStyle::styleHint(sh, w, opt, shr); + } +} + + +bool KStyle::eventFilter( QObject* object, QEvent* event ) +{ + if ( d->useFilledFrameWorkaround ) + { + // Make the QMenuBar/QToolBar paintEvent() cover a larger area to + // ensure that the filled frame contents are properly painted. + // We essentially modify the paintEvent's rect to include the + // panel border, which also paints the widget's interior. + // This is nasty, but I see no other way to properly repaint + // filled frames in all QMenuBars and QToolBars. + // -- Karol. + QFrame *frame = 0; + if ( event->type() == QEvent::Paint + && (frame = ::qt_cast<QFrame*>(object)) ) + { + if (frame->frameShape() != QFrame::ToolBarPanel && frame->frameShape() != QFrame::MenuBarPanel) + return false; + + bool horizontal = true; + QPaintEvent* pe = (QPaintEvent*)event; + QToolBar *toolbar = ::qt_cast< QToolBar *>( frame ); + QRect r = pe->rect(); + + if (toolbar && toolbar->orientation() == Qt::Vertical) + horizontal = false; + + if (horizontal) { + if ( r.height() == frame->height() ) + return false; // Let QFrame handle the painting now. + + // Else, send a new paint event with an updated paint rect. + QPaintEvent dummyPE( QRect( r.x(), 0, r.width(), frame->height()) ); + QApplication::sendEvent( frame, &dummyPE ); + } + else { // Vertical + if ( r.width() == frame->width() ) + return false; + + QPaintEvent dummyPE( QRect( 0, r.y(), frame->width(), r.height()) ); + QApplication::sendEvent( frame, &dummyPE ); + } + + // Discard this event as we sent a new paintEvent. + return true; + } + } + + return false; +} + + +// ----------------------------------------------------------------------------- +// I N T E R N A L - KStyle menu transparency handler +// ----------------------------------------------------------------------------- + +TransparencyHandler::TransparencyHandler( KStyle* style, + TransparencyEngine tEngine, float menuOpacity, bool useDropShadow ) + : QObject() +{ + te = tEngine; + kstyle = style; + opacity = menuOpacity; + dropShadow = useDropShadow; + pix.setOptimization(QPixmap::BestOptim); +} + +TransparencyHandler::~TransparencyHandler() +{ +} + +// This is meant to be ugly but fast. +void TransparencyHandler::rightShadow(QImage& dst) +{ + if (dst.depth() != 32) + dst = dst.convertDepth(32); + + // blend top-right corner. + int pixels = dst.width() * dst.height(); +#ifdef WORDS_BIGENDIAN + register unsigned char* data = dst.bits() + 1; // Skip alpha +#else + register unsigned char* data = dst.bits(); // Skip alpha +#endif + for(register int i = 0; i < 16; i++) { + *data = (unsigned char)((*data)*top_right_corner[i]); data++; + *data = (unsigned char)((*data)*top_right_corner[i]); data++; + *data = (unsigned char)((*data)*top_right_corner[i]); data++; + data++; // skip alpha + } + + pixels -= 32; // tint right strip without rounded edges. + register int c = 0; + for(register int i = 0; i < pixels; i++) { + *data = (unsigned char)((*data)*shadow_strip[c]); data++; + *data = (unsigned char)((*data)*shadow_strip[c]); data++; + *data = (unsigned char)((*data)*shadow_strip[c]); data++; + data++; // skip alpha + ++c; + c %= 4; + } + + // tint bottom edge + for(register int i = 0; i < 16; i++) { + *data = (unsigned char)((*data)*bottom_right_corner[i]); data++; + *data = (unsigned char)((*data)*bottom_right_corner[i]); data++; + *data = (unsigned char)((*data)*bottom_right_corner[i]); data++; + data++; // skip alpha + } +} + +void TransparencyHandler::bottomShadow(QImage& dst) +{ + if (dst.depth() != 32) + dst = dst.convertDepth(32); + + int line = 0; + int width = dst.width() - 4; + double strip_data = shadow_strip[0]; + double* corner = const_cast<double*>(bottom_left_corner); + +#ifdef WORDS_BIGENDIAN + register unsigned char* data = dst.bits() + 1; // Skip alpha +#else + register unsigned char* data = dst.bits(); // Skip alpha +#endif + + for(int y = 0; y < 4; y++) + { + // Bottom-left Corner + for(register int x = 0; x < 4; x++) { + *data = (unsigned char)((*data)*(*corner)); data++; + *data = (unsigned char)((*data)*(*corner)); data++; + *data = (unsigned char)((*data)*(*corner)); data++; + data++; // skip alpha + corner++; + } + + // Scanline + for(register int x = 0; x < width; x++) { + *data = (unsigned char)((*data)*strip_data); data++; + *data = (unsigned char)((*data)*strip_data); data++; + *data = (unsigned char)((*data)*strip_data); data++; + data++; + } + + strip_data = shadow_strip[++line]; + } +} + +// Create a shadow of thickness 4. +void TransparencyHandler::createShadowWindows(const QPopupMenu* p) +{ +#ifdef Q_WS_X11 + int x2 = p->x()+p->width(); + int y2 = p->y()+p->height(); + QRect shadow1(x2, p->y() + 4, 4, p->height()); + QRect shadow2(p->x() + 4, y2, p->width() - 4, 4); + + // Create a fake drop-down shadow effect via blended Xwindows + ShadowElements se; + se.w1 = new QWidget(0, 0, WStyle_Customize | WType_Popup | WX11BypassWM ); + se.w2 = new QWidget(0, 0, WStyle_Customize | WType_Popup | WX11BypassWM ); + se.w1->setGeometry(shadow1); + se.w2->setGeometry(shadow2); + XSelectInput(qt_xdisplay(), se.w1->winId(), StructureNotifyMask ); + XSelectInput(qt_xdisplay(), se.w2->winId(), StructureNotifyMask ); + + // Insert a new ShadowMap entry + shadowMap()[p] = se; + + // Some hocus-pocus here to create the drop-shadow. + QPixmap pix_shadow1 = QPixmap::grabWindow(qt_xrootwin(), + shadow1.x(), shadow1.y(), shadow1.width(), shadow1.height()); + QPixmap pix_shadow2 = QPixmap::grabWindow(qt_xrootwin(), + shadow2.x(), shadow2.y(), shadow2.width(), shadow2.height()); + + QImage img; + img = pix_shadow1.convertToImage(); + rightShadow(img); + pix_shadow1.convertFromImage(img); + img = pix_shadow2.convertToImage(); + bottomShadow(img); + pix_shadow2.convertFromImage(img); + + // Set the background pixmaps + se.w1->setErasePixmap(pix_shadow1); + se.w2->setErasePixmap(pix_shadow2); + + // Show the 'shadow' just before showing the popup menu window + // Don't use QWidget::show() so we don't confuse QEffects, thus causing broken focus. + XMapWindow(qt_xdisplay(), se.w1->winId()); + XMapWindow(qt_xdisplay(), se.w2->winId()); +#else + Q_UNUSED( p ) +#endif +} + +void TransparencyHandler::removeShadowWindows(const QPopupMenu* p) +{ +#ifdef Q_WS_X11 + ShadowMap::iterator it = shadowMap().find(p); + if (it != shadowMap().end()) + { + ShadowElements se = it.data(); + XUnmapWindow(qt_xdisplay(), se.w1->winId()); // hide + XUnmapWindow(qt_xdisplay(), se.w2->winId()); + XFlush(qt_xdisplay()); // try to hide faster + delete se.w1; + delete se.w2; + shadowMap().erase(it); + } +#else + Q_UNUSED( p ) +#endif +} + +bool TransparencyHandler::eventFilter( QObject* object, QEvent* event ) +{ +#if !defined Q_WS_MAC && !defined Q_WS_WIN + // Transparency idea was borrowed from KDE2's "MegaGradient" Style, + // Copyright (C) 2000 Daniel M. Duley <mosfet@kde.org> + + // Added 'fake' menu shadows <04-Jul-2002> -- Karol + QPopupMenu* p = (QPopupMenu*)object; + QEvent::Type et = event->type(); + + if (et == QEvent::Show) + { + // Handle translucency + if (te != Disabled) + { + pix = QPixmap::grabWindow(qt_xrootwin(), + p->x(), p->y(), p->width(), p->height()); + + switch (te) { +#ifdef HAVE_XRENDER + case XRender: + if (qt_use_xrender) { + XRenderBlendToPixmap(p); + break; + } + // Fall through intended +#else + case XRender: +#endif + case SoftwareBlend: + blendToPixmap(p->colorGroup(), p); + break; + + case SoftwareTint: + default: + blendToColor(p->colorGroup().button()); + }; + + p->setErasePixmap(pix); + } + + // Handle drop shadow + // * FIXME : !shadowMap().contains(p) is a workaround for leftover + // * shadows after duplicate show events. + // * TODO : determine real cause for duplicate events + // * till 20021005 + if (dropShadow && p->width() > 16 && p->height() > 16 && !shadowMap().contains( p )) + createShadowWindows(p); + } + else if (et == QEvent::Hide) + { + // Handle drop shadow + if (dropShadow) + removeShadowWindows(p); + + // Handle translucency + if (te != Disabled) + p->setErasePixmap(QPixmap()); + } + +#endif + return false; +} + + +// Blends a QImage to a predefined color, with a given opacity. +void TransparencyHandler::blendToColor(const QColor &col) +{ + if (opacity < 0.0 || opacity > 1.0) + return; + + QImage img = pix.convertToImage(); + KImageEffect::blend(col, img, opacity); + pix.convertFromImage(img); +} + + +void TransparencyHandler::blendToPixmap(const QColorGroup &cg, const QPopupMenu* p) +{ + if (opacity < 0.0 || opacity > 1.0) + return; + + KPixmap blendPix; + blendPix.resize( pix.width(), pix.height() ); + + if (blendPix.width() != pix.width() || + blendPix.height() != pix.height()) + return; + + // Allow styles to define the blend pixmap - allows for some interesting effects. + kstyle->renderMenuBlendPixmap( blendPix, cg, p ); + + QImage blendImg = blendPix.convertToImage(); + QImage backImg = pix.convertToImage(); + KImageEffect::blend(blendImg, backImg, opacity); + pix.convertFromImage(backImg); +} + + +#ifdef HAVE_XRENDER +// Here we go, use XRender in all its glory. +// NOTE: This is actually a bit slower than the above routines +// on non-accelerated displays. -- Karol. +void TransparencyHandler::XRenderBlendToPixmap(const QPopupMenu* p) +{ + KPixmap renderPix; + renderPix.resize( pix.width(), pix.height() ); + + // Allow styles to define the blend pixmap - allows for some interesting effects. + kstyle->renderMenuBlendPixmap( renderPix, p->colorGroup(), p ); + + Display* dpy = qt_xdisplay(); + Pixmap alphaPixmap; + Picture alphaPicture; + XRenderPictFormat Rpf; + XRenderPictureAttributes Rpa; + XRenderColor clr; + clr.alpha = ((unsigned short)(255*opacity) << 8); + + Rpf.type = PictTypeDirect; + Rpf.depth = 8; + Rpf.direct.alphaMask = 0xff; + Rpa.repeat = True; // Tile + + XRenderPictFormat* xformat = XRenderFindFormat(dpy, + PictFormatType | PictFormatDepth | PictFormatAlphaMask, &Rpf, 0); + + alphaPixmap = XCreatePixmap(dpy, p->handle(), 1, 1, 8); + alphaPicture = XRenderCreatePicture(dpy, alphaPixmap, xformat, CPRepeat, &Rpa); + + XRenderFillRectangle(dpy, PictOpSrc, alphaPicture, &clr, 0, 0, 1, 1); + + XRenderComposite(dpy, PictOpOver, + renderPix.x11RenderHandle(), alphaPicture, pix.x11RenderHandle(), // src, mask, dst + 0, 0, // srcx, srcy + 0, 0, // maskx, masky + 0, 0, // dstx, dsty + pix.width(), pix.height()); + + XRenderFreePicture(dpy, alphaPicture); + XFreePixmap(dpy, alphaPixmap); +} +#endif + +void KStyle::virtual_hook( int, void* ) +{ /*BASE::virtual_hook( id, data );*/ } + +// vim: set noet ts=4 sw=4: +// kate: indent-width 4; replace-tabs off; tab-width 4; space-indent off; + +#include "kstyle.moc" diff --git a/kdefx/kstyle.h b/kdefx/kstyle.h new file mode 100644 index 000000000..91d581ac7 --- /dev/null +++ b/kdefx/kstyle.h @@ -0,0 +1,344 @@ +/* + * $Id$ + * + * KStyle + * Copyright (C) 2001-2002 Karol Szwed <gallium@kde.org> + * + * QWindowsStyle CC_ListView and style images were kindly donated by TrollTech, + * Copyright (C) 1998-2000 TrollTech AS. + * + * Many thanks to Bradley T. Hughes for the 3 button scrollbar code. + * + * 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. + */ + +#ifndef __KSTYLE_H +#define __KSTYLE_H + +// W A R N I N G +// ------------- +// This API is still subject to change. +// I will remove this warning when I feel the API is sufficiently flexible. + +#include <qcommonstyle.h> + +#include <kdelibs_export.h> + +class KPixmap; + +struct KStylePrivate; +/** + * Simplifies and extends the QStyle API to make style coding easier. + * + * The KStyle class provides a simple internal menu transparency engine + * which attempts to use XRender for accelerated blending where requested, + * or falls back to fast internal software tinting/blending routines. + * It also simplifies more complex portions of the QStyle API, such as + * the PopupMenuItems, ScrollBars and Sliders by providing extra "primitive + * elements" which are simple to implement by the style writer. + * + * @see QStyle::QStyle + * @see QCommonStyle::QCommonStyle + * @author Karol Szwed (gallium@kde.org) + * @version $Id$ + */ +class KDEFX_EXPORT KStyle: public QCommonStyle +{ + Q_OBJECT + + public: + + /** + * KStyle Flags: + * + * @li Default - Default style setting, where menu transparency + * and the FilledFrameWorkaround are disabled. + * + * @li AllowMenuTransparency - Enable this flag to use KStyle's + * internal menu transparency engine. + * + * @li FilledFrameWorkaround - Enable this flag to facilitate + * proper repaints of QMenuBars and QToolBars when the style chooses + * to paint the interior of a QFrame. The style primitives in question + * are PE_PanelMenuBar and PE_PanelDockWindow. The HighColor style uses + * this workaround to enable painting of gradients in menubars and + * toolbars. + */ + typedef uint KStyleFlags; + enum KStyleOption { + Default = 0x00000000, //!< All options disabled + AllowMenuTransparency = 0x00000001, //!< Internal transparency enabled + FilledFrameWorkaround = 0x00000002 //!< Filled frames enabled + }; + + /** + * KStyle ScrollBarType: + * + * Allows the style writer to easily select what type of scrollbar + * should be used without having to duplicate large amounts of source + * code by implementing the complex control CC_ScrollBar. + * + * @li WindowsStyleScrollBar - Two button scrollbar with the previous + * button at the top/left, and the next button at the bottom/right. + * + * @li PlatinumStyleScrollBar - Two button scrollbar with both the + * previous and next buttons at the bottom/right. + * + * @li ThreeButtonScrollBar - %KDE style three button scrollbar with + * two previous buttons, and one next button. The next button is always + * at the bottom/right, whilst the two previous buttons are on either + * end of the scrollbar. + * + * @li NextStyleScrollBar - Similar to the PlatinumStyle scroll bar, but + * with the buttons grouped on the opposite end of the scrollbar. + * + * @see KStyle::KStyle() + */ + enum KStyleScrollBarType { + WindowsStyleScrollBar = 0x00000000, //!< two button, windows style + PlatinumStyleScrollBar = 0x00000001, //!< two button, platinum style + ThreeButtonScrollBar = 0x00000002, //!< three buttons, %KDE style + NextStyleScrollBar = 0x00000004 //!< two button, NeXT style + }; + + /** + * Constructs a KStyle object. + * + * Select the appropriate KStyle flags and scrollbar type + * for your style. The user's style preferences selected in KControl + * are read by using QSettings and are automatically applied to the style. + * As a fallback, KStyle paints progressbars and tabbars. It inherits from + * QCommonStyle for speed, so don't expect much to be implemented. + * + * It is advisable to use a currently implemented style such as the HighColor + * style as a foundation for any new KStyle, so the limited number of + * drawing fallbacks should not prove problematic. + * + * @param flags the style to be applied + * @param sbtype the scroll bar type + * @see KStyle::KStyleFlags + * @see KStyle::KStyleScrollBarType + * @author Karol Szwed (gallium@kde.org) + */ + KStyle( KStyleFlags flags = KStyle::Default, + KStyleScrollBarType sbtype = KStyle::WindowsStyleScrollBar ); + + /** + * Destructs the KStyle object. + */ + ~KStyle(); + + /** + * Returns the default widget style depending on color depth. + */ + static QString defaultStyle(); + + /** + * Modifies the scrollbar type used by the style. + * + * This function is only provided for convenience. It allows + * you to make a late decision about what scrollbar type to use for the + * style after performing some processing in your style's constructor. + * In most situations however, setting the scrollbar type via the KStyle + * constructor should suffice. + * @param sbtype the scroll bar type + * @see KStyle::KStyleScrollBarType + */ + void setScrollBarType(KStyleScrollBarType sbtype); + + /** + * Returns the KStyle flags used to initialize the style. + * + * This is used solely for the kcmstyle module, and hence is internal. + */ + KStyleFlags styleFlags() const; + + // --------------------------------------------------------------------------- + + /** + * This virtual function defines the pixmap used to blend between the popup + * menu and the background to create different menu transparency effects. + * For example, you can fill the pixmap "pix" with a gradient based on the + * popup's colorGroup, a texture, or some other fancy painting routine. + * KStyle will then internally blend this pixmap with a snapshot of the + * background behind the popupMenu to create the illusion of transparency. + * + * This virtual is never called if XRender/Software blending is disabled by + * the user in KDE's style control module. + */ + virtual void renderMenuBlendPixmap( KPixmap& pix, const QColorGroup& cg, + const QPopupMenu* popup ) const; + + /** + * KStyle Primitive Elements: + * + * The KStyle class extends the Qt's Style API by providing certain + * simplifications for parts of QStyle. To do this, the KStylePrimitive + * elements were defined, which are very similar to Qt's PrimitiveElement. + * + * The first three Handle primitives simplify and extend PE_DockWindowHandle, + * so do not reimplement PE_DockWindowHandle if you want the KStyle handle + * simplifications to be operable. Similarly do not reimplement CC_Slider, + * SC_SliderGroove and SC_SliderHandle when using the KStyle slider + * primitives. KStyle automatically double-buffers slider painting + * when they are drawn via these KStyle primitives to avoid flicker. + * + * @li KPE_DockWindowHandle - This primitive is already implemented in KStyle, + * and paints a bevelled rect with the DockWindow caption text. Re-implement + * this primitive to perform other more fancy effects when drawing the dock window + * handle. + * + * @li KPE_ToolBarHandle - This primitive must be reimplemented. It currently + * only paints a filled rectangle as default behavior. This primitive is used + * to render QToolBar handles. + * + * @li KPE_GeneralHandle - This primitive must be reimplemented. It is used + * to render general handles that are not part of a QToolBar or QDockWindow, such + * as the applet handles used in Kicker. The default implementation paints a filled + * rect of arbitrary color. + * + * @li KPE_SliderGroove - This primitive must be reimplemented. It is used to + * paint the slider groove. The default implementation paints a filled rect of + * arbitrary color. + * + * @li KPE_SliderHandle - This primitive must be reimplemented. It is used to + * paint the slider handle. The default implementation paints a filled rect of + * arbitrary color. + * + * @li KPE_ListViewExpander - This primitive is already implemented in KStyle. It + * is used to draw the Expand/Collapse element in QListViews. To indicate the + * expanded state, the style flags are set to Style_Off, while Style_On implies collapsed. + * + * @li KPE_ListViewBranch - This primitive is already implemented in KStyle. It is + * used to draw the ListView branches where necessary. + */ + enum KStylePrimitive { + KPE_DockWindowHandle, + KPE_ToolBarHandle, + KPE_GeneralHandle, + + KPE_SliderGroove, + KPE_SliderHandle, + + KPE_ListViewExpander, + KPE_ListViewBranch + }; + + /** + * This function is identical to Qt's QStyle::drawPrimitive(), except that + * it adds one further parameter, 'widget', that can be used to determine + * the widget state of the KStylePrimitive in question. + * + * @see KStyle::KStylePrimitive + * @see QStyle::drawPrimitive + * @see QStyle::drawComplexControl + */ + virtual void drawKStylePrimitive( KStylePrimitive kpe, + QPainter* p, + const QWidget* widget, + const QRect &r, + const QColorGroup &cg, + SFlags flags = Style_Default, + const QStyleOption& = QStyleOption::Default ) const; + + + enum KStylePixelMetric { + KPM_MenuItemSeparatorHeight = 0x00000001, + KPM_MenuItemHMargin = 0x00000002, + KPM_MenuItemVMargin = 0x00000004, + KPM_MenuItemHFrame = 0x00000008, + KPM_MenuItemVFrame = 0x00000010, + KPM_MenuItemCheckMarkHMargin = 0x00000020, + KPM_MenuItemArrowHMargin = 0x00000040, + KPM_MenuItemTabSpacing = 0x00000080, + KPM_ListViewBranchThickness = 0x00000100 + }; + + int kPixelMetric( KStylePixelMetric kpm, const QWidget* widget = 0 ) const; + + // --------------------------------------------------------------------------- + + void polish( QWidget* widget ); + void unPolish( QWidget* widget ); + void polishPopupMenu( QPopupMenu* ); + + void drawPrimitive( PrimitiveElement pe, + QPainter* p, + const QRect &r, + const QColorGroup &cg, + SFlags flags = Style_Default, + const QStyleOption& = QStyleOption::Default ) const; + + void drawControl( ControlElement element, + QPainter* p, + const QWidget* widget, + const QRect &r, + const QColorGroup &cg, + SFlags flags = Style_Default, + const QStyleOption& = QStyleOption::Default ) const; + + void drawComplexControl( ComplexControl control, + QPainter *p, + const QWidget* widget, + const QRect &r, + const QColorGroup &cg, + SFlags flags = Style_Default, + SCFlags controls = SC_All, + SCFlags active = SC_None, + const QStyleOption& = QStyleOption::Default ) const; + + SubControl querySubControl( ComplexControl control, + const QWidget* widget, + const QPoint &pos, + const QStyleOption& = QStyleOption::Default ) const; + + QRect querySubControlMetrics( ComplexControl control, + const QWidget* widget, + SubControl sc, + const QStyleOption& = QStyleOption::Default ) const; + + int pixelMetric( PixelMetric m, + const QWidget* widget = 0 ) const; + + QRect subRect( SubRect r, + const QWidget* widget ) const; + + QPixmap stylePixmap( StylePixmap stylepixmap, + const QWidget* widget = 0, + const QStyleOption& = QStyleOption::Default ) const; + + int styleHint( StyleHint sh, + const QWidget* w = 0, + const QStyleOption &opt = QStyleOption::Default, + QStyleHintReturn* shr = 0 ) const; + + protected: + bool eventFilter( QObject* object, QEvent* event ); + + private: + // Disable copy constructor and = operator + KStyle( const KStyle & ); + KStyle& operator=( const KStyle & ); + + protected: + virtual void virtual_hook( int id, void* data ); + private: + KStylePrivate *d; +}; + + +// vim: set noet ts=4 sw=4: +#endif + diff --git a/kdefx/libkdefx.nmcheck b/kdefx/libkdefx.nmcheck new file mode 100644 index 000000000..a0bf7e60c --- /dev/null +++ b/kdefx/libkdefx.nmcheck @@ -0,0 +1,12 @@ +# KDE namespace check file + +# kdefx classes +K*::* + +# these should preferably go in some namespace in KDE4 +kColorBitmaps +kDrawBeButton +kDrawRoundMask +kDrawNextButton +kDrawRoundButton +kRoundMaskRegion diff --git a/kdefx/libkdefx_weak.nmcheck b/kdefx/libkdefx_weak.nmcheck new file mode 100644 index 000000000..6482e1938 --- /dev/null +++ b/kdefx/libkdefx_weak.nmcheck @@ -0,0 +1,3 @@ +# KDE namespace check file + +# KDE classes |