| /* |
| Copyright (C) 2004, 2005 Nikolas Zimmermann <wildfox@kde.org> |
| 2004, 2005 Rob Buis <buis@kde.org> |
| |
| Based on khtml code by: |
| Copyright (C) 1998 Lars Knoll <knoll@kde.org> |
| Copyright (C) 2001-2003 Dirk Mueller <mueller@kde.org> |
| Copyright (C) 2003 Apple Computer, Inc |
| |
| This file is part of the KDE project |
| |
| 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., 59 Temple Place - Suite 330, |
| Boston, MA 02111-1307, USA. |
| */ |
| |
| #include "config.h" |
| #include <assert.h> |
| |
| #include <kdebug.h> |
| #include <kglobal.h> |
| |
| #include <qimage.h> |
| #include <qbitmap.h> |
| #include <qpixmap.h> |
| #include <qpainter.h> |
| #include <qasyncimageio.h> |
| |
| #include "IconData.h" |
| #include "KDOMCache.h" |
| #include "KDOMLoader.h" |
| #include "KDOMCacheHelper.h" |
| #include "KDOMCachedImage.moc" |
| #include "KDOMCachedObjectClient.h" |
| |
| using namespace KDOM; |
| |
| // All supported mimetypes. |
| static QString buildAcceptHeader() |
| { |
| return QString::fromLatin1("image/png, image/jpeg, video/x-mng, image/jp2, image/gif;q=0.5,*/*;q=0.1"); |
| } |
| |
| CachedImage::CachedImage(DocumentLoader *docLoader, const DOMString &url, KIO::CacheControl cachePolicy, const char *) : QObject(), CachedObject(url, Image, cachePolicy, 0) |
| { |
| static const QString &acceptHeader = KGlobal::staticQString(buildAcceptHeader()); |
| setAccept(acceptHeader); |
| |
| m_bg = 0; |
| m_movie = 0; |
| m_pixmap = 0; |
| m_pixPart = 0; |
| |
| m_bgColor = qRgba(0, 0, 0, 0xFF); |
| |
| m_imgSource = 0; |
| m_formatType = 0; |
| |
| m_width = 0; |
| m_height = 0; |
| |
| m_status = Unknown; |
| m_monochrome = false; |
| m_typeChecked = false; |
| m_isFullyTransparent = false; |
| |
| m_showAnimations = docLoader->showAnimations(); |
| |
| /* TODO: Add KHTML Settings class |
| if(KHTMLFactory::defaultHTMLSettings()->isAdFiltered(url.string())) |
| */ |
| if(false) |
| { |
| m_wasBlocked = true; |
| if(!Cache::blockedPixmap) |
| { |
| Cache::blockedPixmap = new QPixmap(); |
| Cache::blockedPixmap->loadFromData(blocked_icon_data, blocked_icon_len); |
| } |
| |
| CachedObject::finish(); |
| } |
| } |
| |
| CachedImage::~CachedImage() |
| { |
| clear(); |
| } |
| |
| const QPixmap &CachedImage::pixmap() const |
| { |
| if(m_hadError) |
| return *Cache::brokenPixmap; |
| |
| if(m_wasBlocked) |
| return *Cache::blockedPixmap; |
| |
| if(m_movie) |
| { |
| if(m_movie->framePixmap().size() != m_movie->getValidRect().size()) |
| { |
| // pixmap is not yet completely loaded, so we |
| // return a clipped version. asserting here |
| // that the valid rect is always from 0/0 to fullwidth/ someheight |
| if(!m_pixPart) |
| m_pixPart = new QPixmap(); |
| |
| (*m_pixPart) = m_movie->framePixmap(); |
| |
| if(m_movie->getValidRect().size().isValid()) |
| m_pixPart->resize(m_movie->getValidRect().size()); |
| else |
| m_pixPart->resize(0, 0); |
| |
| return *m_pixPart; |
| } |
| else |
| return m_movie->framePixmap(); |
| } |
| else if(m_pixmap) |
| return *m_pixmap; |
| |
| return *Cache::nullPixmap; |
| } |
| |
| #define BGMINWIDTH 32 |
| #define BGMINHEIGHT 32 |
| |
| #ifndef APPLE_COMPILE_HACK |
| const QPixmap &CachedImage::tiled_pixmap(const QColor &newc) |
| { |
| static QRgb bgTransparant = qRgba(0, 0, 0, 0xFF); |
| if((m_bgColor != bgTransparant) && (m_bgColor != newc.rgb())) |
| { |
| delete m_bg; |
| m_bg = 0; |
| } |
| |
| if(m_bg) |
| return *m_bg; |
| |
| const QPixmap &r = pixmap(); |
| |
| if(r.isNull()) |
| return r; |
| |
| // no error indication for background images |
| if(m_hadError || m_wasBlocked) |
| return *Cache::nullPixmap; |
| |
| QSize s(pixmap_size()); |
| bool isvalid = newc.isValid(); |
| |
| int w = r.width(); |
| int h = r.height(); |
| if(w * h < 8192) |
| { |
| if(r.width() < BGMINWIDTH) |
| w = ((BGMINWIDTH / s.width()) + 1) * s.width(); |
| |
| if(r.height() < BGMINHEIGHT) |
| h = ((BGMINHEIGHT / s.height()) + 1) * s.height(); |
| } |
| |
| #ifdef Q_WS_X11 |
| if(r.hasAlphaChannel() && ((w != r.width()) || (h != r.height()))) |
| { |
| m_bg = new QPixmap(w, h); |
| |
| // Tile horizontally on the first stripe |
| for(int x = 0; x < w; x += r.width()) |
| copyBlt(m_bg, x, 0, &r, 0, 0, r.width(), r.height()); |
| |
| // Copy first stripe down |
| for(int y = r.height(); y < h; y += r.height()) |
| copyBlt(m_bg, 0, y, m_bg, 0, 0, w, r.height()); |
| |
| return *m_bg; |
| } |
| #endif |
| |
| if( |
| #ifdef Q_WS_X11 |
| !r.hasAlphaChannel() && |
| #endif |
| ((w != r.width()) || (h != r.height()) || (isvalid && r.mask()))) |
| { |
| QPixmap pix = r; |
| if(w != r.width() || (isvalid && pix.mask())) |
| { |
| m_bg = new QPixmap(w, r.height()); |
| |
| QPainter p(m_bg); |
| if(isvalid) |
| p.fillRect(0, 0, w, r.height(), newc); |
| |
| p.drawTiledPixmap(0, 0, w, r.height(), pix); |
| p.end(); |
| |
| if(!isvalid && pix.mask()) |
| { |
| // unfortunately our anti-transparency trick doesn't work here |
| // we need to create a mask. |
| QBitmap newmask(w, r.height()); |
| |
| QPainter pm(&newmask); |
| pm.drawTiledPixmap(0, 0, w, r.height(), *pix.mask()); |
| |
| m_bg->setMask(newmask); |
| m_bgColor = bgTransparant; |
| } |
| else |
| m_bgColor= newc.rgb(); |
| |
| pix = *m_bg; |
| } |
| |
| if(h != r.height()) |
| { |
| delete m_bg; |
| m_bg = new QPixmap(w, h); |
| |
| QPainter p(m_bg); |
| if(isvalid) |
| p.fillRect(0, 0, w, h, newc); |
| |
| p.drawTiledPixmap(0, 0, w, h, pix); |
| if(!isvalid && pix.mask()) |
| { |
| // unfortunately our anti-transparency trick doesn't work here |
| // we need to create a mask. |
| QBitmap newmask(w, h); |
| |
| QPainter pm(&newmask); |
| pm.drawTiledPixmap(0, 0, w, h, *pix.mask()); |
| |
| m_bg->setMask(newmask); |
| m_bgColor = bgTransparant; |
| } |
| else |
| m_bgColor = newc.rgb(); |
| |
| } |
| |
| return *m_bg; |
| } |
| |
| return r; |
| } |
| #endif |
| |
| QSize CachedImage::pixmap_size() const |
| { |
| if(m_wasBlocked) |
| return Cache::blockedPixmap->size(); |
| |
| return (m_hadError ? Cache::brokenPixmap->size() : |
| (m_movie ? m_movie->framePixmap().size() : |
| (m_pixmap ? m_pixmap->size() : QSize()))); |
| } |
| |
| |
| QRect CachedImage::valid_rect() const |
| { |
| if(m_wasBlocked) |
| return Cache::blockedPixmap->rect(); |
| |
| return (m_hadError ? Cache::brokenPixmap->rect() : |
| (m_movie ? m_movie->getValidRect() : |
| (m_pixmap ? m_pixmap->rect() : QRect()))); |
| } |
| |
| |
| void CachedImage::do_notify(const QPixmap &p, const QRect &r) |
| { |
| for(Q3PtrDictIterator<CachedObjectClient> it(m_clients); it.current(); ) |
| it()->setPixmap(p, r, this); |
| } |
| |
| void CachedImage::movieUpdated(const QRect &r) |
| { |
| #ifdef LOADER_DEBUG |
| qDebug("movie updated %d/%d/%d/%d, pixmap size %d/%d", r.x(), r.y(), r.right(), r.bottom(), |
| m_movie->framePixmap().size().width(), |
| m_movie->framePixmap().size().height()); |
| #endif |
| |
| do_notify(m_movie->framePixmap(), r); |
| } |
| |
| #ifndef APPLE_COMPILE_HACK |
| void CachedImage::movieStatus(int status) |
| { |
| #ifdef LOADER_DEBUG |
| qDebug("movieStatus(%d)", status); |
| #endif |
| |
| // ### the html image objects are supposed to send the load event after every frame (according to |
| // netscape). We have a problem though where an image is present, and js code creates a new Image object, |
| // which uses the same CachedImage, the one in the document is not supposed to be notified |
| |
| // just another Qt 2.2.0 bug. we cannot call |
| // QMovie::frameImage if we're after QMovie::EndOfMovie |
| if(status == QMovie::EndOfFrame) |
| { |
| const QImage &im = m_movie->frameImage(); |
| m_monochrome = ((im.depth() <= 8 ) && (im.numColors() - int(im.hasAlphaBuffer()) <= 2)); |
| |
| for(int i = 0; m_monochrome && i < im.numColors(); ++i) |
| { |
| if(im.colorTable()[i] != qRgb(0xff, 0xff, 0xff) && |
| im.colorTable()[i] != qRgb(0x00, 0x00, 0x00)) |
| m_monochrome = false; |
| } |
| |
| if((im.width() < 5 || im.height() < 5) && im.hasAlphaBuffer()) // only evaluate for small images |
| { |
| QImage am = im.createAlphaMask(); |
| if(am.depth() == 1) |
| { |
| bool solid = false; |
| for(int y = 0; y < am.height(); y++) |
| { |
| for(int x = 0; x < am.width(); x++) |
| { |
| if(am.pixelIndex(x, y)) |
| { |
| solid = true; |
| break; |
| } |
| } |
| } |
| |
| m_isFullyTransparent = (!solid); |
| } |
| } |
| |
| // we have to delete our tiled bg variant here |
| // because the frame has changed (in order to keep it in sync) |
| delete m_bg; |
| m_bg = 0; |
| } |
| |
| if((status == QMovie::EndOfMovie && (!m_movie || m_movie->frameNumber() <= 1)) || |
| ((status == QMovie::EndOfLoop) && (m_showAnimations == KDOMSettings::KAnimationLoopOnce)) || |
| ((status == QMovie::EndOfFrame) && (m_showAnimations == KDOMSettings::KAnimationDisabled))) |
| { |
| if(m_imgSource) |
| { |
| setShowAnimations(KDOMSettings::KAnimationDisabled); |
| |
| // monochrome alphamasked images are usually about 10000 times |
| // faster to draw, so this is worth the hack |
| if(m_pixmap && m_monochrome && m_pixmap->depth() > 1) |
| { |
| QPixmap *pix = new QPixmap(); |
| pix->convertFromImage(m_pixmap->convertToImage().convertDepth(1), MonoOnly | AvoidDither); |
| |
| if(m_pixmap->mask()) |
| pix->setMask(*m_pixmap->mask()); |
| |
| delete m_pixmap; |
| m_pixmap= pix; |
| |
| m_monochrome = false; |
| } |
| } |
| |
| for(Q3PtrDictIterator<CachedObjectClient> it(m_clients); it.current();) |
| it()->notifyFinished(this); |
| } |
| } |
| #endif |
| |
| void CachedImage::movieResize(const QSize & /* s */) |
| { |
| do_notify(m_movie->framePixmap(), QRect()); |
| } |
| |
| void CachedImage::deleteMovie() |
| { |
| delete m_movie; |
| m_movie= 0; |
| } |
| |
| #ifndef APPLE_COMPILE_HACK |
| void CachedImage::setShowAnimations(KDOMSettings::KAnimationAdvice showAnimations) |
| { |
| m_showAnimations = showAnimations; |
| |
| if((m_showAnimations == KDOMSettings::KAnimationDisabled) && m_imgSource) |
| { |
| m_imgSource->cleanBuffer(); |
| |
| delete m_pixmap; |
| m_pixmap = new QPixmap(m_movie->framePixmap()); |
| |
| m_movie->disconnectUpdate(this, SLOT(movieUpdated(const QRect &))); |
| m_movie->disconnectStatus(this, SLOT(movieStatus(int))); |
| m_movie->disconnectResize(this, SLOT(movieResize(const QSize &))); |
| |
| QTimer::singleShot(0, this, SLOT(deleteMovie())); |
| |
| m_imgSource = 0; |
| } |
| #endif |
| } |
| |
| void CachedImage::clear() |
| { |
| delete m_bg; m_bg = 0; |
| delete m_movie; m_movie = 0; |
| delete m_pixmap; m_pixmap = 0; |
| delete m_pixPart; m_pixPart = 0; |
| |
| m_bgColor = qRgba(0, 0, 0, 0xFF); |
| |
| m_formatType = 0; |
| m_typeChecked = false; |
| setSize(0); |
| |
| // No need to delete imageSource - QMovie does it for us |
| m_imgSource = 0; |
| } |
| |
| void CachedImage::ref(CachedObjectClient *consumer) |
| { |
| CachedObject::ref(consumer); |
| |
| if(m_movie) |
| { |
| m_movie->unpause(); |
| if(m_movie->finished() || m_clients.count() == 1 ) |
| m_movie->restart(); |
| } |
| |
| // for mouseovers, dynamic changes |
| if(m_status >= Persistent && !valid_rect().isNull()) |
| { |
| consumer->setPixmap(pixmap(), valid_rect(), this); |
| consumer->notifyFinished(this); |
| } |
| } |
| |
| void CachedImage::deref(CachedObjectClient *consumer) |
| { |
| CachedObject::deref(consumer); |
| if(m_movie && m_clients.isEmpty() && m_movie->running()) |
| m_movie->pause(); |
| } |
| |
| void CachedImage::data(QBuffer &buffer, bool eof) |
| #ifndef APPLE_COMPILE_HACK |
| { |
| #ifdef LOADER_DEBUG |
| kdDebug( 6060 ) << this << "in CachedImage::data(buffersize " <<buffer.buffer().size() <<", eof=" << eof << endl; |
| #endif |
| |
| if(!m_typeChecked) |
| { |
| // don't attempt incremental loading if we have all the data already |
| assert(!eof); |
| |
| m_formatType = QImageDecoder::formatName((const unsigned char *) buffer.buffer().data(), buffer.size()); |
| if(m_formatType && strcmp(m_formatType, "PNG") == 0) |
| m_formatType = 0; // Some png files contain multiple images, we want to show only the first one |
| |
| m_typeChecked = true; |
| |
| if(m_formatType) // movie format exists |
| { |
| m_imgSource = new ImageSource(buffer.buffer()); |
| |
| m_movie = new QMovie(m_imgSource, 8192); |
| m_movie->connectUpdate(this, SLOT(movieUpdated(const QRect &))); |
| m_movie->connectStatus(this, SLOT(movieStatus(int))); |
| m_movie->connectResize(this, SLOT(movieResize(const QSize &))); |
| } |
| } |
| |
| if(m_imgSource) |
| { |
| m_imgSource->setEOF(eof); |
| m_imgSource->maybeReady(); |
| } |
| |
| if(eof) |
| { |
| // QMovie currently doesn't support all kinds of image formats |
| // so we need to use a QPixmap here when we finished loading the complete |
| // picture and display it then all at once. |
| if(m_typeChecked && !m_formatType) |
| { |
| #ifdef CACHE_DEBUG |
| kdDebug(6060) << "CachedImage::data(): reloading as pixmap:" << endl; |
| #endif |
| |
| m_pixmap = new QPixmap(buffer.buffer()); |
| |
| #ifdef CACHE_DEBUG |
| kdDebug(6060) << "CachedImage::data(): image is null: " << m_pixmap->isNull() << endl; |
| #endif |
| |
| if(m_pixmap->isNull()) |
| { |
| m_hadError = true; |
| do_notify(pixmap(), QRect(0, 0, 16, 16)); // load "broken image" icon |
| } |
| else |
| do_notify(*m_pixmap, m_pixmap->rect()); |
| |
| for(Q3PtrDictIterator<CachedObjectClient> it(m_clients); it.current();) |
| it()->notifyFinished(this); |
| } |
| #else // APPLE_COMPILE_HACK |
| bool canDraw = false; |
| |
| |
| // Always attempt to load the image incrementally. |
| if (!m_pixmap) |
| m_pixmap = new QPixmap(KWQResponseMIMEType(m_response)); |
| canDraw = m_pixmap->receivedData(buffer.buffer(), eof, m_decoderCallback); |
| |
| // If we have a decoder, we'll be notified when decoding has completed. |
| if (!m_decoderCallback) { |
| if (canDraw || eof) { |
| if (m_pixmap->isNull()) { |
| errorOccured = true; |
| QPixmap ep = pixmap(); |
| do_notify (ep, ep.rect()); |
| Cache::removeCacheEntry (this); |
| } |
| else |
| do_notify(*m_pixmap, m_pixmap->rect()); |
| |
| QSize s = pixmap_size(); |
| setSize(s.width() * s.height() * 2); |
| } |
| if (eof) { |
| m_loading = false; |
| checkNotify(); |
| } |
| } |
| #endif // APPLE_COMPILE_HACK |
| } |
| } |
| |
| void CachedImage::finish() |
| { |
| Status oldStatus = m_status; |
| CachedObject::finish(); |
| |
| if(oldStatus != m_status) |
| { |
| const QPixmap &pm = pixmap(); |
| do_notify(pm, pm.rect()); |
| } |
| |
| QSize s = pixmap_size(); |
| setSize(s.width() * s.height() * 2); |
| } |
| |
| void CachedImage::error(int /* err */, const char * /* text */) |
| { |
| clear(); |
| |
| m_typeChecked = true; |
| m_hadError = true; |
| m_loading = false; |
| |
| do_notify(pixmap(), QRect(0, 0, 16, 16)); |
| |
| for(Q3PtrDictIterator<CachedObjectClient> it( m_clients ); it.current();) |
| it()->notifyFinished(this); |
| } |
| |
| // vim:ts=4:noet |