Support threaded image decoding on machines w/ > 2 CPUs.
Reviewed by Maciej and Chris.
* khtml/misc/loader.cpp:
(CachedImageCallback::notifyUpdate):
(CachedImageCallback::notifyFinished):
(CachedImageCallback::notifyDecodingError):
(CachedImageCallback::handleError):
(CachedImageCallback::clear):
(CachedImage::CachedImage):
(CachedImage::clear):
(CachedImage::data):
(CachedImage::checkNotify):
(Loader::servePendingRequests):
(Loader::slotFinished):
(Loader::numRequests):
(Loader::cancelRequests):
(Loader::removeBackgroundDecodingRequest):
* khtml/misc/loader.h:
(khtml::CachedImageCallback::CachedImageCallback):
(khtml::CachedImageCallback::ref):
(khtml::CachedImageCallback::deref):
(khtml::CachedImage::decoderCallback):
* khtml/rendering/render_object.cpp:
(RenderObject::setPixmap):
* kwq/KWQPixmap.h:
* kwq/KWQPixmap.mm:
(-[WebImageCallback initWithCallback:khtml::]):
(-[WebImageCallback _commonTermination]):
(-[WebImageCallback dealloc]):
(-[WebImageCallback finalize]):
(-[WebImageCallback notify]):
(-[WebImageCallback setImageSourceStatus:]):
(-[WebImageCallback status]):
(QPixmap::shouldUseThreadedDecoding):
(QPixmap::receivedData):
* kwq/WebCoreImageRenderer.h:
* kwq/WebCoreImageRendererFactory.h:
* kwq/WebCoreImageRendererFactory.m:
(+[WebCoreImageRendererFactory shouldUseThreadedDecoding]):
(+[WebCoreImageRendererFactory setShouldUseThreadedDecoding:]):
WebKit:
Support threaded image decoding on machines w/ > 2 CPUs.
Reviewed by Maciej and Chris.
* Misc.subproj/WebKitSystemBits.h:
* Misc.subproj/WebKitSystemBits.m:
(WebSystemMainMemory):
(WebNumberOfCPUs):
* WebCoreSupport.subproj/WebImageData.h:
* WebCoreSupport.subproj/WebImageData.m:
(+[WebImageData initialize]):
(-[WebImageData init]):
(-[WebImageData _commonTermination]):
(-[WebImageData dealloc]):
(-[WebImageData _invalidateImages]):
(-[WebImageData _imageSourceOptions]):
(-[WebImageData imageAtIndex:]):
(-[WebImageData propertiesAtIndex:]):
(-[WebImageData _createImages]):
(-[WebImageData decodeData:isComplete:callback:]):
(-[WebImageData incrementalLoadWithBytes:length:complete:callback:]):
(drawPattern):
(-[WebImageData tileInRect:fromPoint:context:]):
(-[WebImageData isNull]):
(-[WebImageData size]):
(-[WebImageData _frameDurationAt:]):
(-[WebImageData _frameDuration]):
(+[WebImageData stopAnimationsInView:]):
(-[WebImageData addAnimatingRenderer:inView:]):
(-[WebImageData removeAnimatingRenderer:]):
* WebCoreSupport.subproj/WebImageDecodeItem.h: Added.
* WebCoreSupport.subproj/WebImageDecodeItem.m: Added.
(+[WebImageDecodeItem decodeItemWithImage:data:isComplete:callback:]):
(-[WebImageDecodeItem initWithImage:data:isComplete:callback:]):
(-[WebImageDecodeItem finalize]):
(-[WebImageDecodeItem dealloc]):
* WebCoreSupport.subproj/WebImageDecoder.h: Added.
* WebCoreSupport.subproj/WebImageDecoder.m: Added.
(decoderNotifications):
(+[WebImageDecoder initialize]):
(+[WebImageDecoder notifyMainThread]):
(+[WebImageDecoder sharedDecoder]):
(+[WebImageDecoder performDecodeWithImage:data:isComplete:callback:]):
(+[WebImageDecoder imageDecodesPending]):
(+[WebImageDecoder decodeComplete:status:]):
(-[WebImageDecoder init]):
(-[WebImageDecoder dealloc]):
(-[WebImageDecoder finalize]):
(-[WebImageDecoder removeItem]):
(-[WebImageDecoder addItem:]):
(-[WebImageDecoder decodeItem:]):
(decoderThread):
(startDecoderThread):
* WebCoreSupport.subproj/WebImageRenderer.m:
(-[WebImageRenderer initWithData:MIMEType:]):
(-[WebImageRenderer initWithContentsOfFile:]):
(-[WebImageRenderer incrementalLoadWithBytes:length:complete:callback:]):
(-[WebInternalImage incrementalLoadWithBytes:length:complete:callback:]):
* WebKit.pbproj/project.pbxproj:
* WebView.subproj/WebImageRepresentation.m:
(-[WebImageRepresentation receivedData:withDataSource:]):
(-[WebImageRepresentation receivedError:withDataSource:]):
(-[WebImageRepresentation finishedLoadingWithDataSource:]):
WebBrowser:
Added a menu item to enable/disable threaded image decoding.
Reviewed by Maciej and Chris.
* Debug/DebugUtilities.m:
(-[DebugUtilities createDebugMenu]):
(-[BrowserDocument toggleUseATSUForAllTextDrawing:]):
(-[BrowserDocument toggleUseThreadedImageDecoding:]):
(-[BrowserDocument validate_toggleUseThreadedImageDecoding:]):
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@8153
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
+2004-12-07 Richard Williamson <rjw@apple.com>
+
+ Support threaded image decoding on machines w/ > 2 CPUs.
+
+ Reviewed by Maciej and Chris.
+
+ * khtml/misc/loader.cpp:
+ (CachedImageCallback::notifyUpdate):
+ (CachedImageCallback::notifyFinished):
+ (CachedImageCallback::notifyDecodingError):
+ (CachedImageCallback::handleError):
+ (CachedImageCallback::clear):
+ (CachedImage::CachedImage):
+ (CachedImage::clear):
+ (CachedImage::data):
+ (CachedImage::checkNotify):
+ (Loader::servePendingRequests):
+ (Loader::slotFinished):
+ (Loader::numRequests):
+ (Loader::cancelRequests):
+ (Loader::removeBackgroundDecodingRequest):
+ * khtml/misc/loader.h:
+ (khtml::CachedImageCallback::CachedImageCallback):
+ (khtml::CachedImageCallback::ref):
+ (khtml::CachedImageCallback::deref):
+ (khtml::CachedImage::decoderCallback):
+ * khtml/rendering/render_object.cpp:
+ (RenderObject::setPixmap):
+ * kwq/KWQPixmap.h:
+ * kwq/KWQPixmap.mm:
+ (-[WebImageCallback initWithCallback:khtml::]):
+ (-[WebImageCallback _commonTermination]):
+ (-[WebImageCallback dealloc]):
+ (-[WebImageCallback finalize]):
+ (-[WebImageCallback notify]):
+ (-[WebImageCallback setImageSourceStatus:]):
+ (-[WebImageCallback status]):
+ (QPixmap::shouldUseThreadedDecoding):
+ (QPixmap::receivedData):
+ * kwq/WebCoreImageRenderer.h:
+ * kwq/WebCoreImageRendererFactory.h:
+ * kwq/WebCoreImageRendererFactory.m:
+ (+[WebCoreImageRendererFactory shouldUseThreadedDecoding]):
+ (+[WebCoreImageRendererFactory setShouldUseThreadedDecoding:]):
+
2004-12-07 Ken Kocienda <kocienda@apple.com>
Reviewed by John
}
// -------------------------------------------------------------------------------------
+#if APPLE_CHANGES
+void CachedImageCallback::notifyUpdate()
+{
+ if (cachedImage) {
+ cachedImage->do_notify (cachedImage->pixmap(), cachedImage->pixmap().rect());
+ QSize s = cachedImage->pixmap_size();
+ cachedImage->setSize(s.width() * s.height() * 2);
+
+ // After receiving the image header we are guaranteed to know
+ // the image size. Although all of the data may not have arrived or
+ // been decoded we can consider the image loaded for purposed of
+ // layout and dispatching the image's onload handler. Removing the request from
+ // the list of background decoding requests will ensure that Loader::numRequests()
+ // does not count this background request. Further numRequests() can
+ // be correctly used by the part to determine if loading is sufficiently
+ // complete to dispatch the page's onload handler.
+ Request *r = cachedImage->m_request;
+ DocLoader *dl = r->m_docLoader;
+
+ khtml::Cache::loader()->removeBackgroundDecodingRequest(r);
+
+ // Poke the part to get it to do a checkCompleted(). Only do this for
+ // the first update to minimize work. Note that we are guaranteed to have
+ // read the header when we received this first update, which is triggered
+ // by the first kCGImageStatusIncomplete status from CG. kCGImageStatusIncomplete
+ // really means that the CG decoder is waiting for more data, but has already
+ // read the header.
+ if (!headerReceived) {
+ emit khtml::Cache::loader()->requestDone( dl, cachedImage );
+ headerReceived = true;
+ }
+ }
+}
+
+void CachedImageCallback::notifyFinished()
+{
+ if (cachedImage) {
+ cachedImage->do_notify (cachedImage->pixmap(), cachedImage->pixmap().rect());
+ cachedImage->m_loading = false;
+ cachedImage->checkNotify();
+ QSize s = cachedImage->pixmap_size();
+ cachedImage->setSize(s.width() * s.height() * 2);
+
+ Request *r = cachedImage->m_request;
+ DocLoader *dl = r->m_docLoader;
+
+ khtml::Cache::loader()->removeBackgroundDecodingRequest(r);
+
+ // Poke the part to get it to do a checkCompleted().
+ emit khtml::Cache::loader()->requestDone( dl, cachedImage );
+
+ delete r;
+ }
+}
+
+void CachedImageCallback::notifyDecodingError()
+{
+ if (cachedImage) {
+ handleError();
+ }
+}
+
+void CachedImageCallback::handleError()
+{
+ if (cachedImage) {
+ cachedImage->errorOccured = true;
+ QPixmap ep = cachedImage->pixmap();
+ cachedImage->do_notify (ep, ep.rect());
+ Cache::removeCacheEntry (cachedImage);
+
+ clear();
+ }
+}
+
+void CachedImageCallback::clear()
+{
+ if (cachedImage && cachedImage->m_request) {
+ Request *r = cachedImage->m_request;
+ DocLoader *dl = r->m_docLoader;
+
+ khtml::Cache::loader()->removeBackgroundDecodingRequest(r);
+
+ // Poke the part to get it to do a checkCompleted().
+ emit khtml::Cache::loader()->requestFailed( dl, cachedImage );
+
+ delete r;
+ }
+ cachedImage = 0;
+}
+#endif
CachedImage::CachedImage(DocLoader* dl, const DOMString &url, KIO::CacheControl _cachePolicy, time_t _expireDate)
: QObject(), CachedObject(url, Image, _cachePolicy, _expireDate)
setAccept( acceptHeader );
#endif
m_showAnimations = dl->showAnimations();
+#if APPLE_CHANGES
+#if BUILDING_ON_PANTHER
+ m_decoderCallback = 0;
+#else
+ if (QPixmap::shouldUseThreadedDecoding())
+ m_decoderCallback = new CachedImageCallback(this);
+ else
+ m_decoderCallback = 0;
+#endif
+#endif
}
CachedImage::~CachedImage()
// No need to delete imageSource - QMovie does it for us
imgSource = 0;
+
+#if APPLE_CHANGES
+ if (m_decoderCallback) {
+ m_decoderCallback->clear();
+ m_decoderCallback->deref();
+ m_decoderCallback = 0;
+ }
+#endif
}
void CachedImage::data ( QBuffer &_buffer, bool eof )
canDraw = true;
} else {
// Always attempt to load the image incrementally.
- // If the AppKit is unable to decode incrementally this pixmap
- // will not be renderable until all the data has been received.
if (!p)
p = new QPixmap(KWQResponseMIMEType(m_response));
- canDraw = p->receivedData(_buffer.buffer(), eof);
+ canDraw = p->receivedData(_buffer.buffer(), eof, m_decoderCallback);
}
- if (canDraw || eof) {
- if (p->isNull()) {
- errorOccured = true;
- QPixmap ep = pixmap();
- do_notify (ep, ep.rect());
- Cache::removeCacheEntry (this);
- }
- else
- do_notify(*p, p->rect());
+ // If we have a decoder, we'll be notified when decoding has completed.
+ if (!m_decoderCallback) {
+ if (canDraw || eof) {
+ if (p->isNull()) {
+ errorOccured = true;
+ QPixmap ep = pixmap();
+ do_notify (ep, ep.rect());
+ Cache::removeCacheEntry (this);
+ }
+ else
+ do_notify(*p, p->rect());
- QSize s = pixmap_size();
- setSize(s.width() * s.height() * 2);
- }
- if (eof) {
- m_loading = false;
- checkNotify();
+ QSize s = pixmap_size();
+ setSize(s.width() * s.height() * 2);
+ }
+ if (eof) {
+ m_loading = false;
+ checkNotify();
+ }
}
#endif // APPLE_CHANGES
}
if(m_loading) return;
CachedObjectClientWalker w(m_clients);
- while (CachedObjectClient *c = w.next())
+ while (CachedObjectClient *c = w.next()) {
c->notifyFinished(this);
+ }
}
// -------------------------------------------------------------------------------------------
SLOT( slotData( KIO::Job*, const char *, int)));
connect( job, SIGNAL( receivedResponse( KIO::Job *, NSURLResponse *)), SLOT( slotReceivedResponse( KIO::Job *, NSURLResponse *)) );
- if (KWQServeRequest(this, req, job))
+ if (KWQServeRequest(this, req, job)) {
+ if (req->object->type() == CachedObject::Image) {
+ CachedImage *ci = static_cast<CachedImage*>(req->object);
+ if (ci->decoderCallback()) {
+ m_requestsBackgroundDecoding.append(req);
+ }
+ }
m_requestsLoading.insert(job, req);
+ }
#else
connect( job, SIGNAL( data( KIO::Job*, const QByteArray &)),
SLOT( slotData( KIO::Job*, const QByteArray &)));
#endif // APPLE_CHANGES
}
-#if APPLE_CHANGES
+#if !defined(APPLE_CHANGES)
void Loader::slotFinished( KIO::Job* job, NSData *allData)
-#else
-void Loader::slotFinished( KIO::Job* job )
-#endif
{
Request *r = m_requestsLoading.take( job );
KIO::TransferJob* j = static_cast<KIO::TransferJob*>(job);
else
{
r->object->data(r->m_buffer, true);
-#if APPLE_CHANGES
- r->object->setAllData(allData);
-#endif
+
emit requestDone( r->m_docLoader, r->object );
-#if !APPLE_CHANGES
time_t expireDate = j->queryMetaData("expire-date").toLong();
kdDebug(6060) << "Loader::slotFinished, url = " << j->url().url() << " expires " << ctime(&expireDate) << endl;
r->object->setExpireDate(expireDate, false);
-#endif
}
-#if APPLE_CHANGES
- // We don't want to ever finish the load in the case of an error because we don't want them cached.
- if (j->error())
- Cache::removeCacheEntry( r->object );
- else
-#endif
r->object->finish();
#ifdef CACHE_DEBUG
#endif
delete r;
+
servePendingRequests();
}
+#else // APPLE_CHANGES
+void Loader::slotFinished( KIO::Job* job, NSData *allData)
+{
+ Request *r = m_requestsLoading.take( job );
+ KIO::TransferJob* j = static_cast<KIO::TransferJob*>(job);
+
+ if ( !r )
+ return;
+
+ CachedObject *object = r->object;
+ DocLoader *docLoader = r->m_docLoader;
+
+ bool backgroundImageDecoding = (object->type() == CachedObject::Image &&
+ static_cast<CachedImage*>(object)->decoderCallback());
+
+ if (j->error() || j->isErrorPage()) {
+ // Use the background image decoder's callback to handle the error.
+ if (backgroundImageDecoding) {
+ CachedImageCallback *callback = static_cast<CachedImage*>(object)->decoderCallback();
+ callback->handleError();
+ }
+ else {
+ r->object->error( job->error(), job->errorText().ascii() );
+ emit requestFailed( docLoader, object );
+ Cache::removeCacheEntry( object );
+ }
+ }
+ else {
+ object->data(r->m_buffer, true);
+
+ r->object->setAllData(allData);
+
+ // Let the background image decoder trigger the done signal.
+ if (!backgroundImageDecoding)
+ emit requestDone( docLoader, object );
+
+ object->finish();
+ }
+
+ // Let the background image decoder release the request when it is
+ // finished.
+ if (!backgroundImageDecoding) {
+ delete r;
+ }
+
+ servePendingRequests();
+}
+#endif
#if APPLE_CHANGES
if ( lIt.current()->m_docLoader == dl )
res++;
+#if APPLE_CHANGES
+ QPtrListIterator<Request> bdIt( m_requestsBackgroundDecoding );
+ for (; bdIt.current(); ++bdIt )
+ if ( bdIt.current()->m_docLoader == dl )
+ res++;
+#endif
+
return res;
}
else
++lIt;
}
+
+#if APPLE_CHANGES
+ QPtrListIterator<Request> bdIt( m_requestsBackgroundDecoding );
+ while ( bdIt.current() )
+ {
+ if ( bdIt.current()->m_docLoader == dl )
+ {
+ kdDebug( 6060 ) << "cancelling pending request for " << bdIt.current()->object->url().string() << endl;
+ //emit requestFailed( dl, bdIt.current()->object );
+ Cache::removeCacheEntry( bdIt.current()->object );
+ m_requestsBackgroundDecoding.remove( bdIt );
+ }
+ else
+ ++bdIt;
+ }
+#endif
}
+#if APPLE_CHANGES
+void Loader::removeBackgroundDecodingRequest (Request *r)
+{
+ bool present = m_requestsBackgroundDecoding.containsRef(r);
+ if (present) {
+ m_requestsBackgroundDecoding.remove (r);
+ }
+}
+#endif
+
KIO::Job *Loader::jobForRequest( const DOM::DOMString &url ) const
{
QPtrDictIterator<Request> it( m_requestsLoading );
class ImageSource;
+#if APPLE_CHANGES
+ class CachedImage;
+
+ class CachedImageCallback
+ {
+ public:
+ CachedImageCallback (CachedImage *c) : cachedImage(c), refCount(1), headerReceived(false) {};
+
+ void ref() { refCount++; }
+ void deref() { if (--refCount == 0) delete this; }
+
+ void notifyUpdate();
+ void notifyFinished();
+ void notifyDecodingError();
+ void clear();
+ void handleError();
+
+ private:
+ CachedImage *cachedImage;
+ uint refCount;
+ bool headerReceived;
+ };
+#endif
+
/**
* a cached image
*/
#if APPLE_CHANGES
public:
int dataSize() const { return m_dataSize; }
+ CachedImageCallback *decoderCallback() const { return m_decoderCallback; }
private:
+ friend class CachedImageCallback;
+
int m_dataSize;
+ CachedImageCallback *m_decoderCallback;
#endif
};
{
Q_OBJECT
- public:
+ public:
Loader();
~Loader();
int numRequests( DocLoader* dl ) const;
void cancelRequests( DocLoader* dl );
+#if APPLE_CHANGES
+ void removeBackgroundDecodingRequest (Request *r);
+#endif
+
// may return 0L
KIO::Job *jobForRequest( const DOM::DOMString &url ) const;
#endif
signals:
+ friend class CachedImageCallback;
+
void requestStarted( khtml::DocLoader* dl, khtml::CachedObject* obj );
void requestDone( khtml::DocLoader* dl, khtml::CachedObject *obj );
void requestFailed( khtml::DocLoader* dl, khtml::CachedObject *obj );
QPtrList<Request> m_requestsPending;
QPtrDict<Request> m_requestsLoading;
+
+#if APPLE_CHANGES
+ QPtrList<Request> m_requestsBackgroundDecoding;
+#endif
+
#ifdef HAVE_LIBJPEG
KJPEGFormatType m_jpegloader;
#endif
// would avoid putting this function and the CachedObjectClient base class into RenderObject.
if (image && image->pixmap_size() == image->valid_rect().size() && parent()) {
- if (element() && (element()->id() == ID_HTML || element()->id() == ID_BODY))
+ if (canvas() && element() && (element()->id() == ID_HTML || element()->id() == ID_BODY))
canvas()->repaint(); // repaint the entire canvas since the background gets propagated up
else
repaint(); // repaint object, which is a box or a container with boxes inside it
class QWMatrix;
+namespace khtml
+{
+ class CachedImageCallback;
+}
+
bool canRenderImageType(const QString &type);
QPixmap *KWQLoadPixmap(const char *name);
QPixmap &operator=(const QPixmap &);
- bool receivedData(const QByteArray &bytes, bool isComplete);
+ bool receivedData(const QByteArray &bytes, bool isComplete, khtml::CachedImageCallback *decoderCallback);
void stopAnimations();
WebCoreImageRendererPtr image() { return imageRenderer; };
CGImageRef imageRef();
+ static bool shouldUseThreadedDecoding();
+
private:
WebCoreImageRendererPtr imageRenderer;
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+#import "loader.h"
#import "KWQPixmap.h"
#import "KWQFoundationExtras.h"
return [imageRenderer imageRef];
}
-bool QPixmap::receivedData(const QByteArray &bytes, bool isComplete)
+#if !defined(BUILDING_ON_PANTHER)
+@interface WebImageCallback : NSObject
+{
+ khtml::CachedImageCallback *callback;
+ CGImageSourceStatus status;
+}
+- (void)notify;
+- (void)setImageSourceStatus:(CGImageSourceStatus)status;
+- (CGImageSourceStatus)status;
+@end
+@implementation WebImageCallback
+- initWithCallback:(khtml::CachedImageCallback *)c
+{
+ self = [super init];
+ callback = c;
+ c->ref();
+ return self;
+}
+
+- (void)_commonTermination
+{
+ callback->deref();
+}
+
+- (void)dealloc
+{
+ [self _commonTermination];
+ [super dealloc];
+}
+
+- (void)finalize
+{
+ [self _commonTermination];
+ [super finalize];
+}
+
+- (void)notify
+{
+ if (status < kCGImageStatusReadingHeader)
+ callback->notifyDecodingError();
+ else if (status == kCGImageStatusIncomplete) {
+ callback->notifyUpdate();
+ }
+ else if (status == kCGImageStatusComplete) {
+ callback->notifyFinished();
+ }
+}
+
+- (void)setImageSourceStatus:(CGImageSourceStatus)s
+{
+ status = s;
+}
+
+- (CGImageSourceStatus)status
+{
+ return status;
+}
+
+@end
+#endif
+
+bool QPixmap::shouldUseThreadedDecoding()
+{
+ return [WebCoreImageRendererFactory shouldUseThreadedDecoding] ? true : false;
+}
+
+bool QPixmap::receivedData(const QByteArray &bytes, bool isComplete, khtml::CachedImageCallback *decoderCallback)
{
if (imageRenderer == nil) {
imageRenderer = KWQRetain([[WebCoreImageRendererFactory sharedFactory] imageRendererWithMIMEType:MIMEType]);
}
- return [imageRenderer incrementalLoadWithBytes:bytes.data() length:bytes.size() complete:isComplete];
+
+#if !defined(BUILDING_ON_PANTHER)
+ WebImageCallback *callbackWrapper = 0;
+ if (decoderCallback)
+ callbackWrapper = [[WebImageCallback alloc] initWithCallback:decoderCallback];
+
+ bool result = [imageRenderer incrementalLoadWithBytes:bytes.data() length:bytes.size() complete:isComplete callback:callbackWrapper];
+
+ [callbackWrapper release];
+#else
+ bool result = [imageRenderer incrementalLoadWithBytes:bytes.data() length:bytes.size() complete:isComplete callback:0];
+#endif
+
+ return result;
}
bool QPixmap::mask() const
@protocol WebCoreImageRenderer <NSObject, NSCopying>
-- (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete;
+- (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)c;
- (NSSize)size;
- (void)resize:(NSSize)s;
}
+ (WebCoreImageRendererFactory *)sharedFactory;
++ (BOOL)shouldUseThreadedDecoding;
++ (void)setShouldUseThreadedDecoding:(BOOL)flag;
@end
@interface WebCoreImageRendererFactory (SubclassResponsibility) <WebCoreImageRendererFactory>
return sharedFactory;
}
+static BOOL shouldUseThreadedDecoding = NO;
+
++ (BOOL)shouldUseThreadedDecoding
+{
+ return shouldUseThreadedDecoding;
+}
+
++ (void)setShouldUseThreadedDecoding:(BOOL)flag
+{
+ shouldUseThreadedDecoding = flag;
+}
+
- init
{
[super init];
+2004-12-07 Richard Williamson <rjw@apple.com>
+
+ Support threaded image decoding on machines w/ > 2 CPUs.
+
+ Reviewed by Maciej and Chris.
+
+ * Misc.subproj/WebKitSystemBits.h:
+ * Misc.subproj/WebKitSystemBits.m:
+ (WebSystemMainMemory):
+ (WebNumberOfCPUs):
+ * WebCoreSupport.subproj/WebImageData.h:
+ * WebCoreSupport.subproj/WebImageData.m:
+ (+[WebImageData initialize]):
+ (-[WebImageData init]):
+ (-[WebImageData _commonTermination]):
+ (-[WebImageData dealloc]):
+ (-[WebImageData _invalidateImages]):
+ (-[WebImageData _imageSourceOptions]):
+ (-[WebImageData imageAtIndex:]):
+ (-[WebImageData propertiesAtIndex:]):
+ (-[WebImageData _createImages]):
+ (-[WebImageData decodeData:isComplete:callback:]):
+ (-[WebImageData incrementalLoadWithBytes:length:complete:callback:]):
+ (drawPattern):
+ (-[WebImageData tileInRect:fromPoint:context:]):
+ (-[WebImageData isNull]):
+ (-[WebImageData size]):
+ (-[WebImageData _frameDurationAt:]):
+ (-[WebImageData _frameDuration]):
+ (+[WebImageData stopAnimationsInView:]):
+ (-[WebImageData addAnimatingRenderer:inView:]):
+ (-[WebImageData removeAnimatingRenderer:]):
+ * WebCoreSupport.subproj/WebImageDecodeItem.h: Added.
+ * WebCoreSupport.subproj/WebImageDecodeItem.m: Added.
+ (+[WebImageDecodeItem decodeItemWithImage:data:isComplete:callback:]):
+ (-[WebImageDecodeItem initWithImage:data:isComplete:callback:]):
+ (-[WebImageDecodeItem finalize]):
+ (-[WebImageDecodeItem dealloc]):
+ * WebCoreSupport.subproj/WebImageDecoder.h: Added.
+ * WebCoreSupport.subproj/WebImageDecoder.m: Added.
+ (decoderNotifications):
+ (+[WebImageDecoder initialize]):
+ (+[WebImageDecoder notifyMainThread]):
+ (+[WebImageDecoder sharedDecoder]):
+ (+[WebImageDecoder performDecodeWithImage:data:isComplete:callback:]):
+ (+[WebImageDecoder imageDecodesPending]):
+ (+[WebImageDecoder decodeComplete:status:]):
+ (-[WebImageDecoder init]):
+ (-[WebImageDecoder dealloc]):
+ (-[WebImageDecoder finalize]):
+ (-[WebImageDecoder removeItem]):
+ (-[WebImageDecoder addItem:]):
+ (-[WebImageDecoder decodeItem:]):
+ (decoderThread):
+ (startDecoderThread):
+ * WebCoreSupport.subproj/WebImageRenderer.m:
+ (-[WebImageRenderer initWithData:MIMEType:]):
+ (-[WebImageRenderer initWithContentsOfFile:]):
+ (-[WebImageRenderer incrementalLoadWithBytes:length:complete:callback:]):
+ (-[WebInternalImage incrementalLoadWithBytes:length:complete:callback:]):
+ * WebKit.pbproj/project.pbxproj:
+ * WebView.subproj/WebImageRepresentation.m:
+ (-[WebImageRepresentation receivedData:withDataSource:]):
+ (-[WebImageRepresentation receivedError:withDataSource:]):
+ (-[WebImageRepresentation finishedLoadingWithDataSource:]):
+
2004-12-07 Chris Blumenberg <cblu@apple.com>
Fix for performance regression.
#import <Foundation/Foundation.h>
-vm_size_t WebSystemMainMemory(void);
\ No newline at end of file
+vm_size_t WebSystemMainMemory(void);
+int WebNumberOfCPUs(void);
#include <mach/host_info.h>
#include <mach/mach_error.h>
+#include <sys/types.h>
+#include <sys/sysctl.h>
+
static host_basic_info_data_t gHostBasicInfo;
static pthread_once_t initControl = PTHREAD_ONCE_INIT;
pthread_once(&initControl, initCapabilities);
return gHostBasicInfo.memory_size;
}
+
+int WebNumberOfCPUs(void)
+{
+ static int numCPUs = 0;
+
+ if (numCPUs == 0) {
+ int mib[2];
+ size_t len;
+
+ mib[0] = CTL_HW;
+ mib[1] = HW_NCPU;
+ len = sizeof(numCPUs);
+ sysctl(mib, 2, &numCPUs, &len, NULL, 0);
+ }
+ return numCPUs;
+}
{
size_t imagesSize;
CGImageRef *images;
+ CFDictionaryRef *imageProperties;
CGImageSourceRef imageSource;
CGSize size;
size_t frameDurationsSize;
float *frameDurations;
- size_t imagePropertiesSize;
- CFDictionaryRef *imageProperties;
-
size_t currentFrame;
int repetitionsComplete;
BOOL animationFinished;
+
+ NSLock *decodeLock;
}
- (size_t)numberOfImages;
- (CGImageRef)imageAtIndex:(size_t)index;
-- (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete;
-- (void)drawImageAtIndex:(size_t)index inRect:(CGRect)ir fromRect:(CGRect)fr adjustedSize:(CGSize)size compositeOperation:(CGCompositeOperation)op context:(CGContextRef)aContext;
+- (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)c;
- (void)drawImageAtIndex:(size_t)index inRect:(CGRect)ir fromRect:(CGRect)fr compositeOperation:(CGCompositeOperation)op context:(CGContextRef)aContext;
+- (void)drawImageAtIndex:(size_t)index inRect:(CGRect)ir fromRect:(CGRect)fr adjustedSize:(CGSize)size compositeOperation:(CGCompositeOperation)op context:(CGContextRef)aContext;
- (void)tileInRect:(CGRect)rect fromPoint:(CGPoint)point context:(CGContextRef)aContext;
- (BOOL)isNull;
- (CGSize)size;
- (size_t)currentFrame;
- (CFDictionaryRef)propertiesAtIndex:(size_t)index;
+- (void)decodeData:(CFDataRef)data isComplete:(BOOL)f callback:(id)c;
+
@end
#endif
#import <WebKit/WebAssertions.h>
#import <WebKit/WebGraphicsBridge.h>
#import <WebKit/WebImageData.h>
+#import <WebKit/WebImageDecoder.h>
#import <WebKit/WebImageRenderer.h>
#import <WebKit/WebImageRendererFactory.h>
+#import <WebKit/WebKitSystemBits.h>
#import <WebCore/WebCoreImageRenderer.h>
@interface WebImageData (WebInternal)
- (void)_commonTermination;
- (void)_invalidateImages;
-- (void)_invalidateImageProperties;
- (int)_repetitionCount;
- (float)_frameDuration;
- (void)_stopAnimation;
- (void)_nextFrame;
+- (CFDictionaryRef)_imageSourceOptions;
@end
@implementation WebImageData
++ (void)initialize
+{
+ [WebImageRendererFactory setShouldUseThreadedDecoding:(WebNumberOfCPUs() >= 2 ? YES : NO)];
+}
+
+- init
+{
+ self = [super init];
+
+ if ([WebImageRendererFactory shouldUseThreadedDecoding])
+ decodeLock = [[NSLock alloc] init];
+
+ imageSource = CGImageSourceCreateIncremental ([self _imageSourceOptions]);
+
+ return self;
+}
+
+
- (void)_commonTermination
{
ASSERT (!frameTimer);
[self _invalidateImages];
-
- [self _invalidateImageProperties];
-
+
if (imageSource)
CFRelease (imageSource);
- (void)dealloc
{
+ [decodeLock release];
+
[self _commonTermination];
+
[super dealloc];
}
for (i = 0; i < imagesSize; i++) {
if (images[i])
CFRelease (images[i]);
+
+ if (imageProperties[i])
+ CFRelease (imageProperties[i]);
}
free (images);
images = 0;
+ free (imageProperties);
+ imageProperties = 0;
}
}
-- (void)_invalidateImageProperties
+- (CFDictionaryRef)_imageSourceOptions
{
- size_t i;
- for (i = 0; i < imagePropertiesSize; i++) {
- if (imageProperties[i])
- CFRelease (imageProperties[i]);
+ if (!imageSourceOptions) {
+ CFStringRef keys[1] = { kCGImageSourceShouldCache };
+ CFBooleanRef values[1] = { kCFBooleanTrue };
+ imageSourceOptions = CFDictionaryCreate (NULL, (const void **)&keys, (const void **)&values, 1,
+ &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
}
- free (imageProperties);
- imageProperties = 0;
- imagePropertiesSize = 0;
+ return imageSourceOptions;
}
- (CGImageRef)_noColorCorrectionImage:(CGImageRef)image withProperties:(CFDictionaryRef)props;
{
-#if NO_COLOR_CORRECTION
CGColorSpaceRef uncorrectedColorSpace = 0;
CGImageRef noColorCorrectionImage = 0;
}
}
return noColorCorrectionImage;
-#else
- return 0;
-#endif
}
- (CGImageRef)imageAtIndex:(size_t)index
{
- size_t num = [self numberOfImages];
-
- if (imagesSize && num > imagesSize)
- [self _invalidateImages];
-
- if (imageSource) {
- if (index > [self numberOfImages])
- return 0;
+ if (index >= imagesSize)
+ return 0;
-#ifndef NDEBUG
- CGImageSourceStatus containerStatus = CGImageSourceGetStatus(imageSource);
-#endif
- // Ignore status, just try to create the image! Status reported from ImageIO
- // is bogus until the image is created. See 3827851
- //if (containerStatus < kCGImageStatusIncomplete)
- // return 0;
+ return images[index];
+}
-#ifndef NDEBUG
- CGImageSourceStatus imageStatus = CGImageSourceGetStatusAtIndex(imageSource, index);
-#endif
- // Ignore status. Status is invalid until we create the image (and eventually try to display it).
- // See 3827851
- //if (imageStatus < kCGImageStatusIncomplete)
- // return 0;
+- (CFDictionaryRef)propertiesAtIndex:(size_t)index
+{
+ if (index >= imagesSize)
+ return 0;
+
+ return imageProperties[index];
+}
+
+- (void)_createImages
+{
+ size_t i;
- imagesSize = [self numberOfImages];
+ [self _invalidateImages];
+
+ imagesSize = [self numberOfImages];
+ for (i = 0; i < imagesSize; i++) {
if (!images) {
- images = (CGImageRef *)calloc ([self numberOfImages], sizeof(CGImageRef *));
+ images = (CGImageRef *)calloc (imagesSize, sizeof(CGImageRef *));
}
- if (!images[index]) {
- if (!imageSourceOptions) {
- CFStringRef keys[1] = { kCGImageSourceShouldCache };
- CFBooleanRef values[1] = { kCFBooleanTrue };
- imageSourceOptions = CFDictionaryCreate (NULL, (const void **)&keys, (const void **)&values, 1,
- &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
- }
- images[index] = CGImageSourceCreateImageAtIndex (imageSource, index, imageSourceOptions);
- if (images[index] == 0)
- ERROR ("unable to create image at index %d, containerStatus %d, image status %d", (int)index, containerStatus, imageStatus);
+ images[i] = CGImageSourceCreateImageAtIndex (imageSource, i, [self _imageSourceOptions]);
+
+ if (!imageProperties) {
+ imageProperties = (CFDictionaryRef *)malloc (imagesSize * sizeof(CFDictionaryRef));
}
-
-#if NO_COLOR_CORRECTION
- if (imageStatus >= kCGImageStatusIncomplete) {
- CGImageRef noColorCorrectionImage = [self _noColorCorrectionImage:images[index]
- withProperties:[self propertiesAtIndex:index]];
- if (noColorCorrectionImage) {
- CFRelease (images[index]);
- images[index] = noColorCorrectionImage;
- }
- }
-#endif
-
- return images[index];
+
+ imageProperties[i] = CGImageSourceGetPropertiesAtIndex (imageSource, i, 0);
+ if (imageProperties[i])
+ CFRetain (imageProperties[i]);
}
- return 0;
}
-- (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete
+// Called from decoder thread.
+- (void)decodeData:(CFDataRef)data isComplete:(BOOL)f callback:(id)callback
{
- if (!imageSource)
- imageSource = CGImageSourceCreateIncremental (imageSourceOptions);
+ [decodeLock lock];
+
+ CGImageSourceUpdateData (imageSource, data, f);
- [self _invalidateImages];
+ // The work of decoding is actually triggered by image creation.
+ [self _createImages];
+ [decodeLock unlock];
+
+ // Use status from first image to trigger appropriate notification back to WebCore
+ // on main thread.
+ if (callback) {
+ CGImageSourceStatus imageStatus = CGImageSourceGetStatusAtIndex(imageSource, 0);
+
+ // Lie about status. If we have all the data, go ahead and say we're complete
+ // as long we are have at least some valid bands (i.e. >= kCGImageStatusIncomplete).
+ // We have to lie because CG incorrectly reports the status.
+ if (f && imageStatus >= kCGImageStatusIncomplete)
+ imageStatus = kCGImageStatusComplete;
+
+ // Only send bad status if we've read the whole image.
+ if (f || (!f && imageStatus >= kCGImageStatusIncomplete))
+ [WebImageDecoder decodeComplete:callback status:imageStatus];
+ }
+}
+
+- (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)callback
+{
CFDataRef data = CFDataCreate (NULL, bytes, length);
- CGImageSourceUpdateData (imageSource, data, isComplete);
+
+ if (callback) {
+ [WebImageDecoder performDecodeWithImage:self data:data isComplete:isComplete callback:callback];
+ }
+ else {
+ CGImageSourceUpdateData (imageSource, data, isComplete);
+ [self _createImages];
+ }
+
CFRelease (data);
- // Always returns YES because we can't rely on status. See 3827851
- //CGImageSourceStatus status = CGImageSourceGetStatus(imageSource);
- //
- //return status >= kCGImageStatusReadingHeader;
return YES;
}
- (void)drawImageAtIndex:(size_t)index inRect:(CGRect)ir fromRect:(CGRect)fr adjustedSize:(CGSize)adjustedSize compositeOperation:(CGCompositeOperation)op context:(CGContextRef)aContext;
{
+ [decodeLock lock];
+
CGImageRef image = [self imageAtIndex:index];
- if (!image)
+ if (!image) {
+ [decodeLock unlock];
return;
+ }
CGContextSaveGState (aContext);
// clip.
BOOL clipped = NO;
if (h < fr.size.height) {
- fr.size.height = h;
- ir.size.height = h;
- clipped = YES;
+ fr.size.height = h;
+ ir.size.height = h;
+ clipped = YES;
}
// Flip the coords.
}
CGContextRestoreGState (aContext);
+
+ [decodeLock unlock];
}
- (void)drawImageAtIndex:(size_t)index inRect:(CGRect)ir fromRect:(CGRect)fr compositeOperation:(CGCompositeOperation)op context:(CGContextRef)aContext;
static void drawPattern (void * info, CGContextRef context)
{
- CGImageRef image = (CGImageRef)info;
+ WebImageData *data = (WebImageData *)info;
+
+ CGImageRef image = (CGImageRef)[data imageAtIndex:[data currentFrame]];
float w = CGImageGetWidth(image);
float h = CGImageGetHeight(image);
CGContextDrawImage (context, CGRectMake(0, 0, w, h), image);
- (void)tileInRect:(CGRect)rect fromPoint:(CGPoint)point context:(CGContextRef)aContext
{
ASSERT (aContext);
+
+ [decodeLock lock];
CGImageRef image = [self imageAtIndex:[self currentFrame]];
if (!image) {
ERROR ("unable to find image");
+ [decodeLock unlock];
return;
}
fromRect.origin.x = rect.origin.x - oneTileRect.origin.x;
fromRect.origin.y = rect.origin.y - oneTileRect.origin.y;
fromRect.size = rect.size;
+
+ [decodeLock unlock];
[self drawImageAtIndex:[self currentFrame] inRect:rect fromRect:fromRect compositeOperation:kCGCompositeSover context:aContext];
+
return;
}
CGSize phase = CGSizeMake(fmodf(originInWindow.x, w), fmodf(originInWindow.y, h));
// Possible optimization: We may want to cache the CGPatternRef
- CGPatternRef pattern = CGPatternCreate(image, CGRectMake (0, 0, w, h), CGAffineTransformIdentity, w, h,
+ CGPatternRef pattern = CGPatternCreate(self, CGRectMake (0, 0, w, h), CGAffineTransformIdentity, w, h,
kCGPatternTilingConstantSpacing, true, &patternCallbacks);
if (pattern) {
CGContextSaveGState (aContext);
else {
ERROR ("unable to create pattern");
}
+
+ [decodeLock unlock];
}
- (BOOL)isNull
{
if (imageSource)
- return CGImageSourceGetStatus(imageSource) < kCGImageStatusReadingHeader;
+ return CGImageSourceGetStatus(imageSource) < kCGImageStatusReadingHeader;
return YES;
}
float w = 0.f, h = 0.f;
if (!haveSize) {
+ [decodeLock lock];
CFDictionaryRef properties = [self propertiesAtIndex:0];
if (properties) {
CFNumberRef num = CFDictionaryGetValue (properties, kCGImagePropertyPixelWidth);
haveSize = YES;
}
+ [decodeLock unlock];
}
return size;
}
-- (CFDictionaryRef)propertiesAtIndex:(size_t)index
-{
- size_t num = [self numberOfImages];
-
- // Number of images changed!
- if (imagePropertiesSize && num > imagePropertiesSize) {
- // Clear cache.
- [self _invalidateImageProperties];
- }
-
- if (imageProperties == 0 && num) {
- imageProperties = (CFDictionaryRef *)malloc (num * sizeof(CFDictionaryRef));
- size_t i;
- for (i = 0; i < num; i++) {
- imageProperties[i] = CGImageSourceGetPropertiesAtIndex (imageSource, i, 0);
- if (imageProperties[i])
- CFRetain (imageProperties[i]);
- }
- imagePropertiesSize = num;
- }
-
- if (index < num) {
- // If image properties are nil, try to get them again. May have attempted to
- // get them before enough data was available in the header.
- if (imageProperties[index] == 0) {
- imageProperties[index] = CGImageSourceGetPropertiesAtIndex (imageSource, index, 0);
- if (imageProperties[index])
- CFRetain (imageProperties[index]);
- }
-
- return imageProperties[index];
- }
-
- return 0;
-}
-
#define MINIMUM_DURATION (1.0/30.0)
- (float)_frameDurationAt:(size_t)i
{
+ [decodeLock lock];
+
CFDictionaryRef properties = [self propertiesAtIndex:i];
if (!properties) {
+ [decodeLock unlock];
return 0.f;
}
// FIXME: Use constant instead of {GIF}
CFDictionaryRef GIFProperties = CFDictionaryGetValue (properties, @"{GIF}");
if (!GIFProperties) {
+ [decodeLock unlock];
return 0.f;
}
// FIXME: Use constant instead of DelayTime
CFNumberRef num = CFDictionaryGetValue (GIFProperties, @"DelayTime");
if (!num) {
+ [decodeLock unlock];
return 0.f;
}
*/
duration = MINIMUM_DURATION;
}
+
+ [decodeLock unlock];
return duration;
}
}
if (!frameDurations) {
- size_t i, num = [self numberOfImages];
+ size_t i;
frameDurations = (float *)malloc (sizeof(float) * num);
for (i = 0; i < num; i++) {
// Now tell them all to stop drawing.
if (renderersToStop) {
- objectEnumerator = [renderersToStop objectEnumerator];
- while ((renderersInView = [objectEnumerator nextObject])) {
- [renderersInView makeObjectsPerformSelector:@selector(stopAnimation)];
- }
-
- [renderersToStop release];
+ objectEnumerator = [renderersToStop objectEnumerator];
+ while ((renderersInView = [objectEnumerator nextObject])) {
+ [renderersInView makeObjectsPerformSelector:@selector(stopAnimation)];
+ }
+
+ [renderersToStop release];
}
}
- (void)addAnimatingRenderer:(WebImageRenderer *)r inView:(NSView *)view
{
if (!animatingRenderers)
- animatingRenderers = CFDictionaryCreateMutable (NULL, 0, NULL, NULL);
+ animatingRenderers = CFDictionaryCreateMutable (NULL, 0, NULL, &kCFTypeDictionaryValueCallBacks);
NSMutableSet *renderers = (NSMutableSet *)CFDictionaryGetValue (animatingRenderers, view);
if (!renderers) {
renderers = [[NSMutableSet alloc] init];
CFDictionaryAddValue(animatingRenderers, view, renderers);
+ [renderers release];
}
[renderers addObject:r];
if (animatingRenderers && CFDictionaryGetCount(animatingRenderers) == 0) {
[activeAnimations removeObject:self];
- [self _stopAnimation];
+ [self _stopAnimation];
}
}
--- /dev/null
+/*
+ WebImageDecodeItem.h
+ Copyright 2004, Apple, Inc. All rights reserved.
+*/
+#import <WebKit/WebImageData.h>
+
+#ifndef OMIT_TIGER_FEATURES
+
+@interface WebImageDecodeItem : NSObject
+{
+@public
+ WebImageData *imageData;
+ id callback;
+ CFDataRef data;
+ BOOL isComplete;
+}
++ decodeItemWithImage:(WebImageData *)img data:(CFDataRef)d isComplete:(BOOL)f callback:(id)c;
+- initWithImage:(WebImageData *)img data:(CFDataRef)d isComplete:(BOOL)f callback:(id)c;
+@end
+
+#endif
\ No newline at end of file
--- /dev/null
+/*
+ WebImageDecodeItem.m
+ Copyright 2004, Apple, Inc. All rights reserved.
+*/
+#import <WebKit/WebImageDecodeItem.h>
+
+#ifndef OMIT_TIGER_FEATURES
+
+@implementation WebImageDecodeItem
+
++ decodeItemWithImage:(WebImageData *)img data:(CFDataRef)d isComplete:(BOOL)f callback:(id)c
+{
+ return [[[WebImageDecodeItem alloc] initWithImage:img data:d isComplete:f callback:c] autorelease];
+}
+
+- initWithImage:(WebImageData *)img data:(CFDataRef)d isComplete:(BOOL)f callback:(id)c
+{
+ self = [super init];
+ imageData = [img retain];
+ callback = [c retain];
+ data = CFRetain (d);
+ isComplete = f;
+ return self;
+}
+
+- (void)finalize
+{
+ CFRelease (data);
+ [super finalize];
+}
+
+- (void)dealloc
+{
+ [imageData release];
+ [callback release];
+ CFRelease (data);
+ [super dealloc];
+}
+
+@end
+
+#endif
\ No newline at end of file
--- /dev/null
+/*
+ WebImageDecoder.h
+ Copyright 2004, Apple, Inc. All rights reserved.
+*/
+#import <WebKit/WebImageData.h>
+#import <WebKit/WebImageDecodeItem.h>
+
+#ifndef OMIT_TIGER_FEATURES
+
+@interface WebImageDecoder : NSObject
+{
+@public
+ NSConditionLock *itemLock;
+ CFMutableDictionaryRef items;
+ NSLock *completedItemsLock;
+ NSMutableArray *completedItems;
+}
++ (WebImageDecoder *)sharedDecoder;
++ (void)performDecodeWithImage:(WebImageData *)img data:(CFDataRef)d isComplete:(BOOL)f callback:(id)c;
++ (void)decodeComplete:(id)callback status:(CGImageSourceStatus)imageStatus;
++ (BOOL)isImageDecodePending:(WebImageData *)img;
+- (WebImageDecodeItem *)removeItem;
+- (void)addItem:(WebImageDecodeItem *)item;
+- (void)decodeItem:(WebImageDecodeItem *)item;
+@end
+
+#endif
\ No newline at end of file
--- /dev/null
+/*
+ WebImageDecoder.m
+ Copyright 2004, Apple, Inc. All rights reserved.
+*/
+#import <WebKit/WebAssertions.h>
+#import <WebKit/WebImageDecoder.h>
+
+#ifndef OMIT_TIGER_FEATURES
+
+@interface WebImageCallback : NSObject
+- (void)notify;
+- (void)setImageSourceStatus:(CGImageSourceStatus)status;
+- (CGImageSourceStatus)status;
+@end
+
+
+@implementation WebImageDecoder
+
+#define CONDITION_NEW_ITEM_TO_DECODE 1
+#define CONDITION_CHECKED_FOR_ITEMS 2
+
+static void startDecoderThread(void);
+static void *decoderThread(void *arg);
+
+static CFRunLoopSourceRef decoderMainThreadSource;
+static CFRunLoopRef mainRunLoop;
+
+// Don't send partial image update notifications more than
+// every PARTIAL_IMAGE_UPDATE_INTERVAL seconds. The is
+// a global update interval for all pages in all windows.
+// The update notification will trigger a paint, so governing
+// the notification limits the number of intermediate paints
+// for progressive images. Not sending a partial
+// update is harmless.
+//
+// Note the completed notifications are always sent immediately,
+// so, the PARTIAL_IMAGE_UPDATE_INTERVAL will not ever delay
+// drawing of the completely decoded image.
+//
+// We may want to make this interval dynamic based on speed of the
+// machine.
+#define PARTIAL_IMAGE_UPDATE_INTERVAL 0.2
+
+static double lastPartialImageUpdate;
+
+static void decoderNotifications(void *info);
+static void decoderNotifications(void *info)
+{
+ WebImageDecoder *decoder = [WebImageDecoder sharedDecoder];
+
+ ASSERT (mainRunLoop == CFRunLoopGetCurrent());
+
+ [decoder->completedItemsLock lock];
+
+ unsigned i, count = [decoder->completedItems count];
+ WebImageCallback *callback;
+ for (i = 0; i < count; i++) {
+ callback = [decoder->completedItems objectAtIndex:i];
+ if ([callback status] <= kCGImageStatusIncomplete) {
+ double now = CFAbsoluteTimeGetCurrent();
+ if (lastPartialImageUpdate != 0 && lastPartialImageUpdate + PARTIAL_IMAGE_UPDATE_INTERVAL > now)
+ continue;
+ lastPartialImageUpdate = now;
+ }
+ [callback notify];
+ }
+ [decoder->completedItems removeAllObjects];
+
+ [decoder->completedItemsLock unlock];
+}
+
++ (void)initialize
+{
+ // Assumes we are being called from main thread.
+
+ // Create shared decoder.
+ [WebImageDecoder sharedDecoder];
+
+ // Add run loop source to receive notifications when decoding has completed.
+ mainRunLoop = CFRunLoopGetCurrent();
+ CFRunLoopSourceContext sourceContext = {0, self, NULL, NULL, NULL, NULL, NULL, NULL, NULL, decoderNotifications};
+ decoderMainThreadSource = CFRunLoopSourceCreate(NULL, 0, &sourceContext);
+ CFRunLoopAddSource(mainRunLoop, decoderMainThreadSource, kCFRunLoopDefaultMode);
+
+ startDecoderThread();
+}
+
++ (void)notifyMainThread
+{
+ CFRunLoopSourceSignal(decoderMainThreadSource);
+ if (CFRunLoopIsWaiting(mainRunLoop))
+ CFRunLoopWakeUp(mainRunLoop);
+}
+
++ (WebImageDecoder *)sharedDecoder
+{
+ static WebImageDecoder *sharedDecoder = 0;
+
+ if (!sharedDecoder)
+ sharedDecoder = [[WebImageDecoder alloc] init];
+
+ return sharedDecoder;
+}
+
++ (void)performDecodeWithImage:(WebImageData *)img data:(CFDataRef)d isComplete:(BOOL)f callback:(id)c
+{
+ WebImageDecodeItem *item = [WebImageDecodeItem decodeItemWithImage:img data:d isComplete:(BOOL)f callback:c];
+ [[WebImageDecoder sharedDecoder] addItem:item];
+}
+
++ (BOOL)isImageDecodePending:(WebImageData *)img;
+{
+ WebImageDecoder *decoder = [WebImageDecoder sharedDecoder];
+
+ [decoder->itemLock lock];
+ BOOL result = CFDictionaryGetValue (decoder->items, img) ? YES : NO;
+ [decoder->itemLock unlock];
+
+ return result;
+}
+
++ (BOOL)imageDecodesPending
+{
+ WebImageDecoder *decoder = [WebImageDecoder sharedDecoder];
+
+ [decoder->itemLock lock];
+ BOOL result = CFDictionaryGetCount(decoder->items) > 0 ? YES : NO;
+ [decoder->itemLock unlock];
+
+ return result;
+}
+
++ (void)decodeComplete:(id)c status:(CGImageSourceStatus)imageStatus
+{
+ WebImageDecoder *decoder = [WebImageDecoder sharedDecoder];
+ WebImageCallback *callback = (WebImageCallback *)c;
+ [c setImageSourceStatus:imageStatus];
+
+ [decoder->completedItemsLock lock];
+ [decoder->completedItems addObject:callback];
+ [decoder->completedItemsLock unlock];
+
+ [WebImageDecoder notifyMainThread];
+}
+
+
+- init
+{
+ self = [super init];
+ items = CFDictionaryCreateMutable(NULL, 0, NULL, &kCFTypeDictionaryValueCallBacks);
+ itemLock = [[NSConditionLock alloc] init];
+ completedItems = [[NSMutableArray alloc] init];
+ completedItemsLock = [[NSLock alloc] init];
+ return self;
+}
+
+- (void)dealloc
+{
+ CFRelease (items);
+ [itemLock release];
+ [completedItems release];
+ [completedItemsLock release];
+ [super dealloc];
+}
+
+- (void)finalize
+{
+ CFRelease (items);
+ [super finalize];
+}
+
+#define ITEM_STACK_BUFFER_SIZE 128
+
+- (WebImageDecodeItem *)removeItem
+{
+ ASSERT (mainRunLoop != CFRunLoopGetCurrent());
+
+ WebImageDecodeItem *item = 0;
+
+ [itemLock lock];
+
+ CFIndex count = CFDictionaryGetCount(items);
+ if (count) {
+ WebImageData *_keys[ITEM_STACK_BUFFER_SIZE], **keys;
+ WebImageDecodeItem *_values[ITEM_STACK_BUFFER_SIZE], **values;
+ if (count > ITEM_STACK_BUFFER_SIZE) {
+ keys = (WebImageData **)malloc(count * sizeof(WebImageData *));
+ values = (WebImageDecodeItem **)malloc(count * sizeof(WebImageDecodeItem *));
+ }
+ else {
+ keys = _keys;
+ values = _values;
+ }
+ CFDictionaryGetKeysAndValues (items, (const void **)keys, (const void **)values);
+
+ item = _values[0];
+
+ CFDictionaryRemoveValue (items, _keys[0]);
+
+ if (keys != _keys) {
+ free (keys);
+ free (values);
+ }
+ }
+ [itemLock unlock];
+
+ // If we have no item block until we have a new item.
+ if (!item) {
+ [itemLock lockWhenCondition:CONDITION_NEW_ITEM_TO_DECODE];
+ [itemLock unlockWithCondition:CONDITION_CHECKED_FOR_ITEMS];
+ }
+
+ return item;
+}
+
+- (void)addItem:(WebImageDecodeItem *)item
+{
+ ASSERT (mainRunLoop == CFRunLoopGetCurrent());
+
+ [itemLock lock];
+
+ // Add additional reference here to ensure that the main thread's pool
+ // doesn't release the item. It has to stick around long enough for
+ // the decode thread to process it. The decoder thread will remove
+ // the extra reference.
+ [item retain];
+
+ // If a prior decode item was already requested
+ // for the image, remove it. The new item will have more
+ // data. Necessary because CFDictionaryAddValue has a
+ // "add if absent" policy.
+ WebImageDecodeItem *replacementItem = (WebImageDecodeItem *)CFDictionaryGetValue (items, item->imageData);
+ if (replacementItem) {
+ CFDictionaryRemoveValue (items, item->imageData);
+ // Remove additional reference increment when item was added.
+ [replacementItem release];
+ }
+
+ CFDictionaryAddValue (items, item->imageData, item);
+
+ [itemLock unlockWithCondition:CONDITION_NEW_ITEM_TO_DECODE];
+}
+
+- (void)decodeItem:(WebImageDecodeItem *)item
+{
+ ASSERT (mainRunLoop != CFRunLoopGetCurrent());
+
+ [item->imageData decodeData:item->data isComplete:item->isComplete callback:item->callback];
+
+ // Remove additional reference incremented when item was added.
+ [item release];
+}
+
+@end
+
+static void *decoderThread(void *arg)
+{
+ WebImageDecoder *decoder = [WebImageDecoder sharedDecoder];
+ WebImageDecodeItem *item;
+
+ while (true) {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ item = [decoder removeItem];
+ if (item) {
+ [decoder decodeItem:item];
+ }
+
+ [pool release];
+ };
+
+ return 0;
+}
+
+static void startDecoderThread() {
+ pthread_attr_t attr;
+ pthread_t tid;
+ pthread_attr_init(&attr);
+ pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ pthread_create(&tid, &attr, decoderThread, 0);
+ pthread_attr_destroy(&attr);
+}
+
+#endif
\ No newline at end of file
if (self != nil) {
MIMEType = [MIME copy];
imageData = [[WebImageData alloc] init];
- [imageData incrementalLoadWithBytes:[data bytes] length:[data length] complete:YES];
+ [imageData incrementalLoadWithBytes:[data bytes] length:[data length] complete:YES callback:0];
}
return self;
}
imageData = [[WebImageData alloc] init];
NSData *data = [NSData dataWithContentsOfFile:imagePath];
- [imageData incrementalLoadWithBytes:[data bytes] length:[data length] complete:YES];
+ [imageData incrementalLoadWithBytes:[data bytes] length:[data length] complete:YES callback:0];
return self;
return [imageData isNull];
}
-- (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete
+- (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)c
{
if (!imageData)
imageData = [[WebImageData alloc] init];
- return [imageData incrementalLoadWithBytes:bytes length:length complete:isComplete];
+ return [imageData incrementalLoadWithBytes:bytes length:length complete:isComplete callback:c];
}
- (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr
- (NSString *)MIMEType;
- (int)frameCount;
-- (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete;
+- (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)c;
- (void)resize:(NSSize)s;
- (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr;
- (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr compositeOperator:(NSCompositingOperation)compsiteOperator context:(CGContextRef)context;
[self setSize:size];
}
-- (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete
+- (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)c
{
NSArray *reps = [self representations];
NSBitmapImageRep *imageRep = [reps count] > 0 ? [[self representations] objectAtIndex:0] : nil;
[WebInternalImage stopAnimationsInView:aView];
}
-- (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete
+- (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)c
{
- return [image incrementalLoadWithBytes:bytes length:length complete:isComplete];
+ return [image incrementalLoadWithBytes:bytes length:length complete:isComplete callback:c];
}
- (NSSize)size
9305892B070868B300E79D96,
832D7E050709D8FB00F49B61,
83E679790726D7CF006C7A36,
+ 514C4C2E075E7DE500B89CAD,
+ 514C4C30075E7DE500B89CAD,
);
isa = PBXHeadersBuildPhase;
runOnlyForDeploymentPostprocessing = 0;
93B641FB06E292BC0055F610,
51C6513906EFCD9300969825,
832D7E060709D8FB00F49B61,
+ 514C4C2F075E7DE500B89CAD,
+ 514C4C31075E7DE500B89CAD,
);
isa = PBXSourcesBuildPhase;
runOnlyForDeploymentPostprocessing = 0;
);
};
};
+ 514C4C2A075E7DE500B89CAD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ path = WebImageDecodeItem.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 514C4C2B075E7DE500B89CAD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.objc;
+ path = WebImageDecodeItem.m;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 514C4C2C075E7DE500B89CAD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ path = WebImageDecoder.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 514C4C2D075E7DE500B89CAD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.objc;
+ path = WebImageDecoder.m;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 514C4C2E075E7DE500B89CAD = {
+ fileRef = 514C4C2A075E7DE500B89CAD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 514C4C2F075E7DE500B89CAD = {
+ fileRef = 514C4C2B075E7DE500B89CAD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 514C4C30075E7DE500B89CAD = {
+ fileRef = 514C4C2C075E7DE500B89CAD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 514C4C31075E7DE500B89CAD = {
+ fileRef = 514C4C2D075E7DE500B89CAD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
5152FADD033FC50400CA2ACD = {
fileEncoding = 4;
isa = PBXFileReference;
F5E7B24603025CE801A80180,
BE26F18D05517E0800BFA0C3,
BE26F18E05517E0800BFA0C3,
+ 514C4C2A075E7DE500B89CAD,
+ 514C4C2B075E7DE500B89CAD,
+ 514C4C2C075E7DE500B89CAD,
+ 514C4C2D075E7DE500B89CAD,
51C6513606EFCD9300969825,
51C6513706EFCD9300969825,
9CE1F8A002A5C6F30ECA2ACD,
- (void)receivedData:(NSData *)data withDataSource:(WebDataSource *)theDataSource
{
NSData *allData = [dataSource data];
- [image incrementalLoadWithBytes:[allData bytes] length:[allData length] complete:NO];
+ [image incrementalLoadWithBytes:[allData bytes] length:[allData length] complete:NO callback:0];
}
- (void)receivedError:(NSError *)error withDataSource:(WebDataSource *)theDataSource
{
NSData *allData = [dataSource data];
if ([allData length] > 0) {
- [image incrementalLoadWithBytes:[allData bytes] length:[allData length] complete:YES];
+ [image incrementalLoadWithBytes:[allData bytes] length:[allData length] complete:YES callback:0];
}
doneLoading = YES;
}
- (void)finishedLoadingWithDataSource:(WebDataSource *)theDataSource
{
NSData *allData = [dataSource data];
- [image incrementalLoadWithBytes:[allData bytes] length:[allData length] complete:YES];
+ [image incrementalLoadWithBytes:[allData bytes] length:[allData length] complete:YES callback:0];
doneLoading = YES;
}