/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ /* -*- Mode: C; tab-width: 4 -*- * ilclient.c --- Management of imagelib client data structures, * including image cache. * * $Id: ilclient.cpp,v 3.10 1999-06-09 20:19:29 pnunn%netscape.com Exp $ */ #include "if.h" #include "il_strm.h" /* For OPAQUE_CONTEXT. */ #include "nsIImgDecoder.h" #include "nsImgDCallbk.h" #include "ilISystemServices.h" /* for XP_GetString() */ #include "xpgetstr.h" PR_BEGIN_EXTERN_C extern int MK_OUT_OF_MEMORY; extern int XP_MSG_IMAGE_NOT_FOUND; extern int XP_MSG_XBIT_COLOR; extern int XP_MSG_1BIT_MONO; extern int XP_MSG_XBIT_GREYSCALE; extern int XP_MSG_XBIT_RGB; extern int XP_MSG_DECODED_SIZE; extern int XP_MSG_WIDTH_HEIGHT; extern int XP_MSG_SCALED_FROM; extern int XP_MSG_IMAGE_DIM; extern int XP_MSG_COLOR; extern int XP_MSG_NB_COLORS; extern int XP_MSG_NONE; extern int XP_MSG_COLORMAP; extern int XP_MSG_BCKDRP_VISIBLE; extern int XP_MSG_SOLID_BKGND; extern int XP_MSG_JUST_NO; extern int XP_MSG_TRANSPARENCY; extern int XP_MSG_COMMENT; extern int XP_MSG_UNKNOWN; extern int XP_MSG_COMPRESS_REMOVE; PR_END_EXTERN_C static uint32 image_cache_size; #ifndef M12N /* XXXM12N Cache trace: cleanup/eliminate. */ static int il_cache_trace = FALSE; /* XXXM12N Clean up/eliminate */ #endif PRLogModuleInfo *il_log_module = NULL; ilISystemServices *il_ss = NULL; /* simple list, in use order */ struct il_cache_struct { il_container *head; il_container *tail; int32 bytes; int32 max_bytes; int items; }; struct il_cache_struct il_cache; /*---------------------------------------------*/ /*-------------------------------*/ NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID); NS_DEFINE_IID(kIFactoryIID, NS_IFACTORY_IID); NS_IMPL_ISUPPORTS(ImgDCallbk, kImgDCallbkIID) NS_IMETHODIMP ImgDCallbk::CreateInstance(const nsCID &aClass, il_container *ic, const nsIID &aIID, void **ppv) { ImgDCallbk *imgdcb = NULL; *ppv = NULL; if (&aClass && !aIID.Equals(kISupportsIID)) return NS_NOINTERFACE; imgdcb = new ImgDCallbk(ic); /* make sure interface = nsISupports, ImgDCallbk */ nsresult res = imgdcb->QueryInterface(aIID,(void**)ppv); if (NS_FAILED(res)) { *ppv = NULL; delete imgdcb; } return res; } /*-------------------------*/ /* Add an image container to a context's container list. */ static PRBool il_add_container_to_context(IL_GroupContext *img_cx, il_container *ic) { il_container_list *ic_list; ic_list = PR_NEWZAP(il_container_list); if (!ic_list) return PR_FALSE; ic_list->ic = ic; ic_list->next = img_cx->container_list; img_cx->container_list = ic_list; return PR_TRUE; } /* Remove an image container from a context's container list. */ static PRBool il_remove_container_from_context(IL_GroupContext *img_cx, il_container *ic) { il_container_list *current, *next; current = img_cx->container_list; if (!current) return PR_FALSE; if (current->ic == ic) { img_cx->container_list = current->next; PR_FREEIF(current); return PR_TRUE; } else { for (; current; current = next) { next = current->next; if (next && (next->ic == ic)) { current->next = next->next; PR_FREEIF(next); return PR_TRUE; } } } return PR_FALSE; } /* Returns PR_TRUE if the context didn't belong to the container's client context list, and was successfully added to that list. */ static PRBool il_add_client_context(IL_GroupContext *img_cx, il_container *ic) { il_context_list *current = ic->img_cx_list; /* Check whether the context is already in the client context list. */ if (current) for (; current; current = current->next) { if (current->img_cx == img_cx) return PR_FALSE; } if (il_add_container_to_context(img_cx, ic)) { /* If the client image context doesn't already belong to the container's client context list, then add it to the beginning of the list. */ current = PR_NEWZAP(il_context_list); if (!current) return PR_FALSE; current->img_cx = img_cx; current->next = ic->img_cx_list; ic->img_cx_list = current; /* Increment the number of containers in this context. */ img_cx->num_containers++; /* If the image is still loading, increment the number of loading containers in the context. Observer notification takes place if the image group previously had no actively loading images. */ if (IMAGE_CONTAINER_ACTIVE(ic) && !ic->is_looping) { PR_ASSERT(!ic->is_aborted); if (!img_cx->num_loading) il_group_notify(img_cx, IL_STARTED_LOADING); img_cx->num_loading++; } /* If the image is looping, increment the number of looping containers in the context. Observer notification takes place if the image group previously had no looping images. */ if (ic->is_looping) { if (!img_cx->num_looping) il_group_notify(img_cx, IL_STARTED_LOOPING); img_cx->num_looping++; } #ifdef DEBUG_GROUP_OBSERVER ILTRACE(1,("5 img_cx=%x total=%d loading=%d looping=%d aborted=%d", img_cx, img_cx->num_containers, img_cx->num_loading, img_cx->num_looping, img_cx->num_aborted)); #endif /* DEBUG_GROUP_OBSERVER */ return PR_TRUE; } else { return PR_FALSE; } } /* Returns PR_TRUE if the client context belonged to the container's client context list, and was successfully deleted from that list. */ static PRBool il_remove_client_context(IL_GroupContext *img_cx, il_container *ic) { PRBool deleted_element = PR_FALSE; il_context_list *current, *next; current = ic->img_cx_list; if (!current) return PR_FALSE; if (current->img_cx == img_cx) { ic->img_cx_list = current->next; PR_FREEIF(current); deleted_element = PR_TRUE; } else { for (; current; current = next) { next = current->next; if (next && (next->img_cx == img_cx)) { current->next = next->next; PR_FREEIF(next); deleted_element = PR_TRUE; break; } } } if (deleted_element && il_remove_container_from_context(img_cx, ic)) { /* Decrement the number of containers in this context. */ img_cx->num_containers--; /* If the image didn't load successfully, then decrement the number of loading containers in the context. (If the image did load successfully, the number of loading containers in the context was previously decremented at the time the container loaded, so don't do it again.) Observer notification takes place if this was the last image loading in the group. */ if (!(ic->state == IC_COMPLETE || ic->is_looping)) { img_cx->num_loading--; if (!img_cx->num_loading) il_group_notify(img_cx, IL_FINISHED_LOADING); if (ic->is_aborted) img_cx->num_aborted--; } /* If the image was looping, then decrement the number of looping containers in the context. Observer notification takes place if this was the last looping image in the group. */ if (ic->is_looping) { img_cx->num_looping--; if (!img_cx->num_looping) il_group_notify(img_cx, IL_FINISHED_LOOPING); } #ifdef DEBUG_GROUP_OBSERVER ILTRACE(1,("6 img_cx=%x total=%d loading=%d looping=%d aborted=%d", img_cx, img_cx->num_containers, img_cx->num_loading, img_cx->num_looping, img_cx->num_aborted)); #endif /* DEBUG_GROUP_OBSERVER */ return PR_TRUE; } else { return PR_FALSE; } } /* Returns TRUE if image container appears to match search parameters */ static int il_image_match(il_container *ic, /* Candidate for match. */ IL_DisplayType display_type, const char *image_url, IL_IRGB *background_color, int req_depth, /* Colorspace depth. */ int req_width, /* Target image width. */ int req_height) /* Target image height. */ { PRBool ic_sized = (PRBool)(ic->state >= IC_SIZED); NI_PixmapHeader *img_header = &ic->image->header; IL_IRGB *img_trans_pixel = img_header->transparent_pixel; /* Allowable conditions for there to be a size match. req_width and req_height both zero indicate usage of the image's natural size. If only one is zero, it indicates scaling maintaining the image's aspect ratio. If ic_size has been called for the image cache entry, then ic->dest_width and ic->dest_height represent the size of the image as it will be displayed. If ic_size has not been called for the cache entry, then ic->dest_width and ic->dest_height represent the requested size. */ if (!( /* Both dimensions match (either zero is a match.) */ ((req_width == ic->dest_width) && (req_height == ic->dest_height)) || /* Width matches, request height zero, aspect ratio same. */ (ic_sized && (req_width == ic->dest_width) && !req_height && !ic->aspect_distorted) || /* Height matches, request width zero, aspect ratio same. */ (ic_sized && (req_height == ic->dest_height) && !req_width && !ic->aspect_distorted) || /* Request dimensions zero, cache entry has natural dimensions. */ (!req_width && !req_height && ic->natural_size) )) return FALSE; /* We allow any depth image through as the FE may have asked us to decode to a colorspace other than the display colorspace. */ /* Now check the background color. This only applies to transparent images, so skip this test if the candidate for the match is known to be opaque. */ if (!ic_sized || img_trans_pixel) { if (background_color) { if (ic->background_color) { /* Both request and candidate have a background color; check whether they match. */ if (background_color->red != ic->background_color->red) return FALSE; if (background_color->green != ic->background_color->green) return FALSE; if (background_color->blue != ic->background_color->blue) return FALSE; } else { /* A background color was requested, but the candidate does not have one. */ return FALSE; } } else { /* No background color was requested. */ if (ic->background_color) { /* A background color was not requested, but the candidate has one. This means that while the current request may need a mask, the candidate definitely does not have one. */ return FALSE; } else { /* Neither the request nor the candidate have a background color, so don't disqualify the candidate. */ } } } /* Check the url (we have already checked the hash value which is based on the url.) */ if (strcmp(image_url, ic->url_address)) return FALSE; /* Printer contexts and window contexts may have different native image formats, so don't reuse an image cache entry created for an onscreen context in a printer context or vice-versa. */ if (display_type != ic->display_type) return FALSE; if((ic->display_type==IL_Printer) && (ic->dest_width != ic->image->header.width) && (ic->dest_height != ic->image->header.height )) return FALSE; /* XXX - temporary */ if (ic->rendered_with_custom_palette) return FALSE; return TRUE; } static int il_images_match(il_container *ic1, il_container *ic2) { return il_image_match(ic1, ic2->display_type, ic2->url_address, ic2->background_color, ic2->image->header.color_space->pixmap_depth, ic2->dest_width, ic2->dest_height); } static il_container * il_find_in_cache(IL_DisplayType display_type, uint32 hash, const char *image_url, IL_IRGB* background_color, int req_depth, int req_width, int req_height) { il_container *ic=0; PR_ASSERT(hash); for (ic=il_cache.head; ic; ic=ic->next) { if (ic->hash != hash) continue; if (il_image_match(ic, display_type, image_url, background_color, req_depth, req_width, req_height)) break; } if (ic) { ILTRACE(2,("il: found ic=0x%08x in cache\n", ic)); return ic; } return NULL; } static void il_addtocache(il_container *ic); il_container * il_get_container(IL_GroupContext *img_cx, NET_ReloadMethod cache_reload_policy, const char *image_url, IL_IRGB *background_color, IL_DitherMode dither_mode, int req_depth, int req_width, /* Target width requested by client. */ int req_height) /* Target height requested by client. */ { uint32 urlhash, hash; il_container *ic; int result; urlhash = hash = il_hash(image_url); /* * Take into account whether color rendering preferences have * changed since the image was cached. */ hash += 21117 * (int)dither_mode; /* Check the cache */ ic = il_find_in_cache(img_cx->display_type, hash, image_url, background_color, req_depth, req_width, req_height); if (ic) { /* We already started to discard this image container. Make a new one.*/ if ((ic->state == IC_ABORT_PENDING)) ic = NULL; /* Check if we have to reload or if there's an expiration date */ /* and we've expired or if this is a JavaScript generated image. */ /* We don't want to cache JavaScript generated images because: */ /* 1) The assumption is that they are generated and might change */ /* if the page is reloaded. */ /* 2) Their namespace crosses document boundaries, so caching */ /* could result in incorrect behavior. */ else if((cache_reload_policy == NET_SUPER_RELOAD) || ((cache_reload_policy == NET_NORMAL_RELOAD) && (!ic->forced)) || (cache_reload_policy != NET_CACHE_ONLY_RELOAD && ic->expires && (time(NULL) > ic->expires)) ) { /* Get rid of the old copy of the image that we're replacing. */ if (!ic->is_in_use) { il_removefromcache(ic); il_delete_container(ic); } ic = NULL; } } /* Reorder the cache list so it remains in LRU order*/ if (ic) il_removefromcache(ic); /* There's no existing matching container. Make a new one. */ if (!ic) { ic = PR_NEWZAP(il_container); if (!ic) return NULL; /* Allocate the source image header. */ if (!(ic->src_header = PR_NEWZAP(NI_PixmapHeader))) { PR_FREEIF(ic); return NULL; } /* Allocate the source image's colorspace. The fields will be filled in by the image decoder. */ if (!(ic->src_header->color_space = PR_NEWZAP(IL_ColorSpace))) { PR_FREEIF(ic->src_header); PR_FREEIF(ic); return NULL; } IL_AddRefToColorSpace(ic->src_header->color_space); /* Allocate the destination image structure. A destination mask structure will be allocated later if the image is determined to be transparent and no background_color has been provided. */ if (!(ic->image = PR_NEWZAP(IL_Pixmap))) { IL_ReleaseColorSpace(ic->src_header->color_space); PR_FREEIF(ic->src_header); PR_FREEIF(ic); return NULL; } /* Set the destination image colorspace to be the source image's colorspace. The Front Ends can override this with the display colorspace. */ ic->image->header.color_space = ic->src_header->color_space; IL_AddRefToColorSpace(ic->image->header.color_space); /* Save the requested background color in the container. */ if (background_color) { if (!(ic->background_color = PR_NEWZAP(IL_IRGB))) { PR_FREEIF(ic->image); IL_ReleaseColorSpace(ic->src_header->color_space); IL_ReleaseColorSpace(ic->image->header.color_space); PR_FREEIF(ic->src_header); PR_FREEIF(ic); return NULL; } XP_MEMCPY(ic->background_color, background_color, sizeof(IL_IRGB)); } else { ic->background_color = NULL; } ILTRACE(2, ("il: create ic=0x%08x\n", ic)); ic->hash = hash; ic->urlhash = urlhash; ic->url_address = PL_strdup(image_url); ic->is_url_loading = PR_FALSE; ic->dest_width = req_width; ic->dest_height = req_height; /* The image context is saved for use during decoding only. */ ic->img_cx = img_cx; /* The type of display for which this image is being decoded e.g. screen, printer, postscript. */ ic->display_type = img_cx->display_type; /* Certain callbacks, like DestroyPixmap, can be invoked after the image context has been destroyed, so we hold on to the callback interface and increment its reference count. The reference count is decremented when the container is destroyed. */ ic->img_cb = img_cx->img_cb; NS_ADDREF(ic->img_cb); /* callbacks for the image decoders */ ImgDCallbk* imgdcb; imgdcb = new ImgDCallbk(ic); NS_ADDREF(imgdcb); nsresult res = imgdcb->QueryInterface(kImgDCallbkIID, (void**)&imgdcb); if (NS_FAILED(res)) { if(imgdcb) *imgdcb = NULL; delete imgdcb; return NULL; } imgdcb->SetContainer(ic); ic->imgdcb = imgdcb; NS_ADDREF(ic->imgdcb); } il_addtocache(ic); ic->is_in_use = TRUE; return ic; } void il_scour_container(il_container *ic) { /* scour quantize, ds, scalerow etc */ if (ic->image->header.color_space->type == NI_PseudoColor) il_free_quantize(ic); FREE_IF_NOT_NULL(ic->scalerow); /* reset a bunch of stuff */ ic->url = NULL; if (ic->net_cx) NS_RELEASE(ic->net_cx); ic->net_cx = NULL; ic->forced = FALSE; ic->is_alone = FALSE; } /* * destroy an il_container, freeing it and the image */ void il_delete_container(il_container *ic) { PR_ASSERT(ic); if (ic) { ILTRACE(2,("il: delete ic=0x%08x", ic)); /* * We can't throw away this container until the netlib * stops sending us stuff. We defer the actual deletion * of the container until then. */ if (ic->is_url_loading) { ic->state = IC_ABORT_PENDING; return; } PR_ASSERT(ic->clients == NULL); il_scour_container(ic); PR_FREEIF(ic->background_color); PR_FREEIF(ic->src_header->transparent_pixel); IL_ReleaseColorSpace(ic->src_header->color_space); PR_FREEIF(ic->src_header); /* delete the image */ if (!(ic->image || ic->mask)) return; il_destroy_pixmap(ic->img_cb, ic->image); if (ic->mask) il_destroy_pixmap(ic->img_cb, ic->mask); NS_RELEASE(ic->img_cb); FREE_IF_NOT_NULL(ic->comment); FREE_IF_NOT_NULL(ic->url_address); FREE_IF_NOT_NULL(ic->fetch_url); PR_FREEIF(ic); } } /* Destroy an IL_Pixmap. */ void il_destroy_pixmap(ilIImageRenderer *img_cb, IL_Pixmap *pixmap) { NI_PixmapHeader *header = &pixmap->header; /* Free the pixmap's bits, as well as the client_data. */ img_cb->DestroyPixmap(NULL, pixmap); /* Release memory used by the header. */ IL_ReleaseColorSpace(header->color_space); PR_FREEIF(header->transparent_pixel); /* Release the IL_Pixmap. */ PR_FREEIF(pixmap); } static char * il_visual_info(il_container *ic) { char *msg = (char *)PR_Calloc(1, 50); NI_PixmapHeader *img_header = &ic->image->header; if (!msg) return NULL; switch (img_header->color_space->type) { case NI_PseudoColor: XP_SPRINTF(msg, XP_GetString(XP_MSG_XBIT_COLOR), img_header->color_space->pixmap_depth); /* #### i18n */ break; case NI_GreyScale: if (img_header->color_space->pixmap_depth == 1) XP_SPRINTF(msg, XP_GetString(XP_MSG_1BIT_MONO)); /* #### i18n */ else XP_SPRINTF(msg, XP_GetString(XP_MSG_XBIT_GREYSCALE), img_header->color_space->pixmap_depth); /* #### i18n */ break; case NI_TrueColor: XP_SPRINTF(msg, XP_GetString(XP_MSG_XBIT_RGB), img_header->color_space->pixmap_depth); /* #### i18n */ break; default: PR_ASSERT(0); *msg=0; break; } return msg; } /* Define some macros to help us output HTML */ #define CELL_TOP \ StrAllocCat(output, \ "