#include "ImageCache.hxx" namespace ImageCache { //--------------------------------------------------------------------------- void on_loader_frame_done(GdkPixbufLoader *loader, gpointer arg1, gpointer user_data) { ImageCache *imageCache = (ImageCache *)gtk_object_get_user_data(GTK_OBJECT(loader)); imageCache->loaderImageDone((CachedURX *)user_data); } //--------------------------------------------------------------------------- CachedURX::CachedURX() { loader = NULL; } CachedURX::~CachedURX() { if (loader != NULL) delete loader; } CachedLOD *CachedURX::getCachedLOD(int lod) { return cachedLODs[lod]; } GdkPixbuf *CachedURX::getLoaderPixbuf() { return (loader == NULL ? NULL : gdk_pixbuf_loader_get_pixbuf(loader)); } void CachedURX::setCachedLOD(int lod, CachedLOD *cl) { if (cl == NULL) cachedLODs.erase(lod); else cachedLODs[lod] = cl; } void CachedURX::write(char *data, int len, ImageCache *imageCache) { if (loader == NULL) { loader = gdk_pixbuf_loader_new(); gtk_object_set_user_data(GTK_OBJECT(loader), imageCache); gtk_signal_connect(GTK_OBJECT(loader), "frame-done", GTK_SIGNAL_FUNC(on_loader_frame_done), this); } gdk_pixbuf_loader_write(loader, (guchar *)data, len); } void CachedURX::closeLoader() { if (loader != NULL) { gdk_pixbuf_loader_close(loader); loader = NULL; } } //--------------------------------------------------------------------------- void CachedLOD::createScaled(GdkPixbuf *from, double scale) { int orig_width = gdk_pixbuf_get_width (from); int orig_height = gdk_pixbuf_get_height(from); int width = (int)(scale * orig_width ); if (width < 1) width = 1; int height = (int)(scale * orig_height); if (height < 1) height = 1; pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, true, 8, width, height); gdk_pixbuf_ref(pixbuf); gdk_pixbuf_scale(from, pixbuf, 0, 0, width, height, 0, 0, scale, scale, GDK_INTERP_BILINEAR); size = width*height*4; } CachedLOD::CachedLOD(int newLOD, CachedURX *cu, gboolean final) { next = NULL; prev = NULL; lod = newLOD; cachedURX = cu; createScaled(cu->getLoaderPixbuf(), 1.0 / (1 << lod)); fullyLoaded = final; cachedURX->setCachedLOD(lod, this); } CachedLOD::CachedLOD(int newLOD, CachedLOD *cl) { next = NULL; prev = NULL; cachedURX = cl->getCachedURX(); lod = newLOD; int orig_lod = cl->getLOD(); int lod_dif = orig_lod - lod; if (lod_dif >= 0) { createScaled(cl->pixbuf, 1.0 / (1 << lod_dif)); fullyLoaded = cl->isFullyLoaded(); } else { createScaled(cl->pixbuf, 1 << (-lod_dif)); fullyLoaded = false; } cachedURX->setCachedLOD(lod, this); } CachedLOD::~CachedLOD() { cachedURX->setCachedLOD(lod, NULL); gdk_pixbuf_unref(pixbuf); } CachedLOD *CachedLOD::getNext() { return next; } CachedLOD *CachedLOD::getPrev() { return prev; } void CachedLOD::setNext(CachedLOD *cl) { next = cl; } void CachedLOD::setPrev(CachedLOD *cl) { prev = cl; } GdkPixbuf *CachedLOD::getPixbuf() { return pixbuf; } int CachedLOD::getSize() { return size; } CachedURX *CachedLOD::getCachedURX() { return cachedURX; } int CachedLOD::getLOD() { return lod; } gboolean CachedLOD::isFullyLoaded() { return fullyLoaded; } void CachedLOD::update(GdkPixbuf *from, gboolean final) { if ((pixbuf == NULL) || fullyLoaded) return; int width = gdk_pixbuf_get_width (pixbuf); int height = gdk_pixbuf_get_height(pixbuf); double scale = 1.0 / (1 << lod); gdk_pixbuf_scale(from, pixbuf, 0, 0, width, height, 0, 0, scale, scale, GDK_INTERP_BILINEAR); fullyLoaded = final; } //--------------------------------------------------------------------------- ImageCache::ImageCache() { recentlyUsedLODs = NULL; size = 0; maxSize = 10*1024*1024; loaderPixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, false, 8, 16, 16); gdk_pixbuf_ref(loaderPixbuf); } ImageCache::~ImageCache() { CachedURX *cu; CachedLOD *cl = recentlyUsedLODs; CachedLOD *oldcl; while (cl != NULL) { cu = cl->getCachedURX(); cu->closeLoader(); oldcl = cl; cl = cl->getNext(); delete oldcl; } cachedURXs.clear(); gdk_pixbuf_unref(loaderPixbuf); } /** * Create new level of detail from another level * and put it on top of the recentlyUsedLODs list. */ CachedLOD *ImageCache::createCachedLOD(int lod, CachedLOD *oldcl) { CachedLOD *cl = new CachedLOD(lod, oldcl); size += cl->getSize(); cl->setNext(recentlyUsedLODs); cl->getNext()->setPrev(cl); recentlyUsedLODs = cl; return cl; } /** * Create a new level of detail from a (LOD 0) pixbuf * and put it on top of the recentlyUsedLODs list. */ CachedLOD *ImageCache::createCachedLOD(int lod, CachedURX *cu, gboolean final) { CachedLOD *cl = new CachedLOD(lod, cu, final); size += cl->getSize(); cl->setNext(recentlyUsedLODs); cl->getNext()->setPrev(cl); recentlyUsedLODs = cl; return cl; } /** * Move a level of detail to the top of the recentlyUsedLODs list. */ void ImageCache::useCachedLOD(CachedLOD *cl) { if (cl->getNext() != NULL) cl->getNext()->setPrev(cl->getPrev()); if (cl->getPrev() != NULL) cl->getPrev()->setNext(cl->getNext()); cl->setPrev(NULL); cl->setNext(recentlyUsedLODs); cl->getNext()->setPrev(cl); recentlyUsedLODs = cl; } GdkPixbuf *ImageCache::getPixbuf(string urx, int lod) { CachedLOD *wantedLOD; CachedLOD *gotLOD; CachedURX *wantedURX = cachedURXs[urx]; //----------------------------------------------------------------- // If the wanted URX was not found... //----------------------------------------------------------------- if (wantedURX == NULL) { // Start loading! // backgroundLoader.startLoading(urx); XXXXXXX return loaderPixbuf; } //----------------------------------------------------------------- // The wanted URX was found, how about the Level of Detail? //----------------------------------------------------------------- wantedLOD = wantedURX->getCachedLOD(lod); //----------------------------------------------------------------- // If the wanted LOD was not found... //----------------------------------------------------------------- if (wantedLOD == NULL) { // See if it can be resized from another LOD // Try to find one that is bigger, and fully loaded... for (int tryLOD = lod-1; tryLOD >= 0; tryLOD--) { gotLOD = wantedURX->getCachedLOD(tryLOD); if ((gotLOD != NULL) && (gotLOD->isFullyLoaded())) { wantedLOD = createCachedLOD(lod, gotLOD); return wantedLOD->getPixbuf(); } } // There was no fully loaded one, so try the pixbufloader: GdkPixbuf *pb = wantedURX->getLoaderPixbuf(); if (pb != NULL) { wantedLOD = createCachedLOD(lod, wantedURX, false); return wantedLOD->getPixbuf(); } // Finally check the few next levels for smaller versions // if one is found, copy it and start loading a better one for (int tryLOD = lod+1; tryLOD < lod+6; tryLOD++) { gotLOD = wantedURX->getCachedLOD(tryLOD); if ((gotLOD != NULL) && (gotLOD->isFullyLoaded())) { wantedLOD = createCachedLOD(lod, gotLOD); // backgroundLoader.startLoading(urx); XXXXXXX return wantedLOD->getPixbuf(); } } return loaderPixbuf; } //----------------------------------------------------------------- // If the wanted LOD is not fully loaded: //----------------------------------------------------------------- if (!wantedLOD->isFullyLoaded()) { // Check if it's being loaded in the pixbufloader: GdkPixbuf *pb = wantedURX->getLoaderPixbuf(); if (pb != NULL) { wantedLOD->update(pb, false); useCachedLOD(wantedLOD); return wantedLOD->getPixbuf(); } // See if it can be resized from another LOD: for (int tryLOD = lod-1; tryLOD >= 0; tryLOD--) { gotLOD = wantedURX->getCachedLOD(tryLOD); if ((gotLOD != NULL) && (gotLOD->isFullyLoaded())) { wantedLOD->update(gotLOD->getPixbuf(), true); useCachedLOD(wantedLOD); return wantedLOD->getPixbuf(); } } // The pixbufloader's area has not been prepared yet? return loaderPixbuf; } //----------------------------------------------------------------- // The wanted LOD was fully loaded: //----------------------------------------------------------------- useCachedLOD(wantedLOD); // move to top of recentlyUsedLODs return wantedLOD->getPixbuf(); //----------------------------------------------------------------- } int ImageCache::getWidth(string urx, int lod) { return gdk_pixbuf_get_width(getPixbuf(urx,lod)); } int ImageCache::getHeight(string urx, int lod) { return gdk_pixbuf_get_height(getPixbuf(urx,lod)); } int *ImageCache::getPixels(string urx, int lod) { return (int *)gdk_pixbuf_get_pixels(getPixbuf(urx,lod)); } void ImageCache::write(string urx, char *data, int len) { CachedURX *wantedURX = cachedURXs[urx]; if (wantedURX == NULL) { wantedURX = new CachedURX(); cachedURXs[urx] = wantedURX; } wantedURX->write(data, len, this); } void ImageCache::loaderImageDone(CachedURX *cu) { CachedLOD *cl = cu->getCachedLOD(0); if (cl == NULL) { cl = createCachedLOD(0, cu, true); } else { cl->update(cu->getLoaderPixbuf(), true); useCachedLOD(cl); } cu->closeLoader(); } //--------------------------------------------------------------------------- }