/* * Copyright (c) 2004 Nokia. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the * distribution. * * Neither the name of Nokia nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include "ImageRenderer.h" #include "CGContextProvider.h" #ifndef _WIN32 #include "GdkXftContext.h" #else #include "Win32Context.h" #endif #include "GdkHelpers.h" extern "C"{ static void size_prepared(GdkPixbufLoader *loader, gint width, gint height, gpointer user_data); static void area_prepared(GdkPixbufLoader *loader, gpointer user_data); static void area_updated(GdkPixbufLoader *loader, gint arg1, gint arg2, gint arg3, gint arg4, gpointer user_data); static void closed(GdkPixbufLoader *loader,gpointer user_data); #if 0 // never used static gboolean anim_timeout(gpointer user_data); static gboolean update_timeout(gpointer user_data); #endif } const int ImageRenderer::updateTimeoutMS = 100; /** ImageRenderer class for loading images and rendering images * * invariants: * pixbuf: valid pixbuf if * image should be regarded as still image * or image is still being loaded * 0 if image is animation and loaded * anim: animation data if * image is still being loaded * or image is valid animation * 0 if image is still image and loaded * * iter: valid animation iterator if image is animation and loaded * 0 image is still picture, object owns pixbuf * * loader: valid GdkPixbufLoader if stuff is being still loaded * 0 if data is not being loaded * * timeout_moment: valid only if iter != 0 * * NOTE: stopping the animation with stopAnimation() doesn't work * this is due to the fact that core stops animationions when * assigning QPixmaps to each other. as the animations are * implicitly shared. if lvalue == rvalue in assignment, the * picture stays stopped * */ ImageRenderer::ImageRenderer() :loader(0) ,anim(0) ,iter(0) ,pixbuf(0) ,pixmap(0) ,alpha(0) ,cached(false) ,tileCached(false) ,offset(0) ,stopped(false) { fillGdkRectangle(&wantedSize, 0, 0, -1,-1); } ImageRenderer::ImageRenderer(const ImageRenderer& ir) :loader(0) ,anim(0) ,iter(0) ,pixbuf(0) ,pixmap(0) ,alpha(0) ,cached(false) ,tileCached(false) ,offset(0) ,stopped(false) { wantedSize = ir.wantedSize; if (ir.loader) { // still loading, treat as still picture but may have animation data // FIXME: Loader is not "copied", so what this means is that from copied ImageRenderers // where the loading is still on we will have only still images!! // we should only connect to signals but not "own" the loader. assert(!ir.iter); assert(ir.pixbuf); pixbuf = ir.pixbuf; g_object_ref(pixbuf); } else { // not loading #if 0 // animations are disabled if (ir.anim) { // is an animation => get own pixbuf, owned by iterator anim = ir.anim; g_object_ref(anim); assert(ir.iter); iter = gdk_pixbuf_animation_get_iter(anim, NULL); } else { #endif // not an animation assert(!ir.iter); pixbuf = ir.pixbuf; if (pixbuf) g_object_ref(pixbuf); #if 0 } #endif } } ImageRenderer::ImageRenderer(GdkPixbuf* abuf) :loader(0) ,anim(0) ,iter(0) ,pixbuf(abuf) ,pixmap(0) ,alpha(0) ,cached(false) ,tileCached(false) ,offset(0) ,stopped(false) { fillGdkRectangle(&wantedSize,0,0,-1,-1); if (pixbuf) g_object_ref(pixbuf); realSize(&wantedSize); } void ImageRenderer::connectHandlers() { assert(loader); loader.connect("area-prepared", ::area_prepared, this); loader.connect("size-prepared", ::size_prepared, this); loader.connect("area-updated", ::area_updated, this); loader.connect("closed", ::closed, this); } ImageRenderer::~ImageRenderer() { if (loader) gdk_pixbuf_loader_close(loader, NULL); invalidate(); if (iter) g_object_unref(iter); if (anim) g_object_unref(anim); if (pixbuf) g_object_unref(pixbuf); } bool ImageRenderer::incrementalLoadWithBytes(const void *bytes, unsigned length, bool isComplete) { GError* err = NULL; if (!loader) { loader = gdk_pixbuf_loader_new(); offset = 0; connectHandlers(); } if ((length - offset) > 0) { bool succ = gdk_pixbuf_loader_write(loader, ((guchar*) bytes) + offset, length - offset, &err); if (succ) { offset += length - offset; } else { // error occured. loader is closed. // note. This usually means that new loader will be created on next chunck of data // and load is tried again. maybe this will slow us down, an we should have flag // indicating that this ImageRenderer instance is invalid loader = 0; } } if (isComplete) { if (loader) { err = NULL; gdk_pixbuf_loader_close(loader, &err); loader = 0; } } return anim != NULL || pixbuf != NULL; } void ImageRenderer::realSize(GdkRectangle* rect) { if (iter) { fillGdkRectangle(rect, 0, 0, gdk_pixbuf_animation_get_width(anim), gdk_pixbuf_animation_get_height(anim)); } else if (pixbuf) { fillGdkRectangle(rect, 0, 0, gdk_pixbuf_get_width(pixbuf), gdk_pixbuf_get_height(pixbuf)); } else { fillGdkRectangle(rect, 0, 0, -1, -1); } } void ImageRenderer::size(GdkRectangle* outSize) { assert(outSize); *outSize = wantedSize; } void ImageRenderer::resize(GdkRectangle* s) { assert(s); assert(s->x == 0); assert(s->y == 0); assert(s->width > 0); assert(s->height > 0); invalidate(); wantedSize = *s; } GdkPixbuf* ImageRenderer::getCurrentPixbuf() const { if (iter) { return gdk_pixbuf_animation_iter_get_pixbuf(iter); } return pixbuf; } // Scales image or animation frame if resize() has been called. // Sets ImageRenderer::pixmap and ImageRenderer::alpha accordingly, so the image/frame can be rendered void ImageRenderer::cache() { animate(); if (cached) return; assert(!pixmap); assert(!alpha); cached = true; GdkPixbuf* buf; buf = getCurrentPixbuf(); GdkRectangle realsz; realSize(&realsz); if (wantedSize.width != realsz.width || wantedSize.height != realsz.height) { GdkPixbuf* scaledbuf = gdk_pixbuf_scale_simple(buf, wantedSize.width, wantedSize.height, GDK_INTERP_BILINEAR); gdk_pixbuf_render_pixmap_and_mask_for_colormap(scaledbuf,gdk_colormap_get_system(), &pixmap, &alpha, 100); buf = scaledbuf; if (iter) { // animation -> discard scaled frame contents, only preserve the serverside representations g_object_unref(scaledbuf); } else { // still image -> scaled image becomes the representation of this image g_object_unref(pixbuf); pixbuf = scaledbuf; } return; } else { //correct size gdk_pixbuf_render_pixmap_and_mask_for_colormap(buf, gdk_colormap_get_system(),&pixmap, &alpha, 100); } assert(gdk_pixbuf_get_has_alpha(buf) == (alpha != NULL)); } void ImageRenderer::tileCache() { cache(); // tilecache not implemented yet tileCached = true; } void ImageRenderer::invalidate() { if (pixmap) { g_object_unref(pixmap); pixmap = 0; } if (alpha) { g_object_unref(alpha); alpha = 0; } cached = false; // invalidate tileCache tileCached = false; } void ImageRenderer::drawImageInRect(GdkRectangle* inRect, GdkRectangle* fromRect, NSCompositingOperation compositeOperator, CGContextRef context) { // scaling not supported assert(inRect->width == fromRect->width); assert(inRect->height == fromRect->height); if (isNull()) return; cache(); if (alpha) { gdk_gc_set_clip_mask(context->gc, alpha); gdk_gc_set_clip_origin(context->gc, inRect->x - fromRect->x, inRect->y - fromRect->y); } int x, y, sw, sh, sx, sy; if (!context->_clip || !alpha) { // no clip or clip but not alpha --> draw directly x = inRect->x; y = inRect->y; sx = fromRect->x; sy = fromRect->y; sw = fromRect->width; sh = fromRect->height; gdk_draw_drawable(context->drawable, context->gc, pixmap, sx, sy, x, y, sw, sh); } else { // gdk clipping doesn't work when alpha is set --> manual clipping GdkRegion* dst_region = gdk_region_rectangle(inRect); gdk_region_intersect(dst_region, context->_clip); GdkRectangle* areas = 0; gint n_areas = 0; gdk_region_get_rectangles(dst_region, &areas, &n_areas); if (n_areas>0) { int i; for (i=0;ix + (x - inRect->x); // source offsets of the image rendering sy = fromRect->y + (y - inRect->y); // ie. how many pixels were skipped because of clip sw = areas[i].width; sh = areas[i].height; gdk_draw_drawable(context->drawable, context->gc, pixmap, sx, sy, x, y, sw, sh); } } if (areas) g_free(areas); // Gdk API doesn't say whether areas allocated regardless of the result or not gdk_region_destroy(dst_region); } if (alpha) { gdk_gc_set_clip_mask(context->gc, NULL); gdk_gc_set_clip_origin(context->gc, 0, 0); gdk_gc_set_clip_region(context->gc, context->_clip); } if (iter && !stopped) { #ifndef _WIN32 static_cast(context)->regionExpiresAt(&timeout_moment, inRect); #else static_cast(context)->regionExpiresAt(&timeout_moment, inRect); #endif } } void ImageRenderer::stopAnimation() { stopped = true; } void ImageRenderer::tileInRect(GdkRectangle* r, int sx, int sy, CGContextRef context) { assert(r); assert(context); if (isNull()) return; int x, y, w,h; int sw = wantedSize.width; int sh = wantedSize.height; x = r->x; y = r->y; w = r->width; h = r->height; tileCache(); if (!alpha) { // special case without alpha, we can use tiling acceleration // (don't know if this makes a difference) // assume gdk clipping works. gdk_gc_set_tile(context->gc, pixmap); gdk_gc_set_fill(context->gc, GDK_TILED); // start copy from (sx,sy), destination (x,y) // tile origin is relative to dest. drawable gdk_gc_set_ts_origin(context->gc, x - sx, y - sy); gdk_draw_rectangle(context->drawable, context->gc, TRUE, x, y, w, h); gdk_gc_set_fill(context->gc, GDK_SOLID); } else { // with alpha, do tiling by hand gdk_gc_set_clip_mask(context->gc, alpha); // remaining width, height int rw,rh; // current x, .. y. current source x, .. y, .. width, .. height int cx, cy, csx, csy, csw,csh; cy = y; rh = h; csy = sy; csh = MIN(sh - sy, h); while ( csh > 0 ) { cx = x; csx = sx; csw = MIN(sw - sx, w); // draw row with height of csh pixels, width of w rw = w; while (csw > 0) { // draw csw x csh pixels gdk_gc_set_clip_origin(context->gc, cx - csx, cy - csy); gdk_draw_drawable(context->drawable, context->gc, pixmap, csx, csy, cx, cy, csw, csh); csx = 0; cx += csw; rw -= csw; csw = MIN(rw, sw); } csy = 0; cy += csh; rh -= csh; csh = MIN(rh, sh); } //restore old clip region gdk_gc_set_clip_mask(context->gc, NULL); gdk_gc_set_clip_origin(context->gc, 0, 0); // assume that clip origin is always 0,0 gdk_gc_set_clip_region(context->gc, context->_clip); } if (iter && !stopped) { #ifndef _WIN32 static_cast(context)->regionExpiresAt(&timeout_moment, r); #else static_cast(context)->regionExpiresAt(&timeout_moment, r); #endif } } bool ImageRenderer::isNull() { if (!pixbuf && !anim) return true; return false; } void ImageRenderer::increaseUseCount() { } void ImageRenderer::decreaseUseCount() { } void ImageRenderer::flushRasterCache() { invalidate(); } void ImageRenderer::animate() { if (stopped) return; if (!iter) return; // still image bool need_update = gdk_pixbuf_animation_iter_advance(iter, NULL); if (need_update) { invalidate(); } gint timeout = gdk_pixbuf_animation_iter_get_delay_time(iter); // milliseconds if (timeout >= 0) { g_get_current_time(&timeout_moment); g_time_val_add(&timeout_moment, (timeout*1000)); // microseconds } else { stopped = true; } } void ImageRenderer::loaderClosed() { if (!anim && !pixbuf) return; if (gdk_pixbuf_animation_is_static_image(anim)) { g_object_unref(anim); anim = 0; return; } iter = gdk_pixbuf_animation_get_iter(anim, NULL); guint timeout = gdk_pixbuf_animation_iter_get_delay_time(iter); // milliseconds if (timeout <= 0) { g_object_unref(iter); g_object_unref(anim); anim = 0; iter = 0; return; } invalidate(); g_object_unref(pixbuf); pixbuf = 0; g_get_current_time(&timeout_moment); g_time_val_add(&timeout_moment, (timeout*1000)); // microseconds animate(); } void ImageRenderer::sizePrepared(int width, int height) { if (wantedSize.width == -1 && wantedSize.height == -1) { wantedSize.width = width; wantedSize.height = height; return; } if (wantedSize.width == width && wantedSize.height == height) return; gdk_pixbuf_loader_set_size(loader, wantedSize.width, wantedSize.height); } void ImageRenderer::areaPrepared() { if (!anim) { anim = gdk_pixbuf_loader_get_animation(loader); if (anim) g_object_ref(anim); if (pixbuf) g_object_unref(pixbuf); pixbuf = gdk_pixbuf_loader_get_pixbuf(loader); if (pixbuf) g_object_ref(pixbuf); } } void ImageRenderer::areaUpdated(int x, int y, int w, int h) { invalidate(); } ImageRenderer* ImageRenderer::copy() { ImageRenderer* r = new ImageRenderer(*this); return r; } GdkPixbuf* ImageRenderer::handle() const { return getCurrentPixbuf(); } extern "C"{ static void area_prepared(GdkPixbufLoader *loader, gpointer user_data) { ImageRenderer* d = static_cast(user_data); d->areaPrepared(); } static void area_updated(GdkPixbufLoader *loader, gint arg1, gint arg2, gint arg3, gint arg4, gpointer user_data) { ImageRenderer* d = static_cast(user_data); d->areaUpdated(arg1,arg2,arg3,arg4); } static void closed(GdkPixbufLoader *loader,gpointer user_data) { ImageRenderer* d = static_cast(user_data); d->loaderClosed(); } static void size_prepared(GdkPixbufLoader *loader, gint width, gint height, gpointer user_data) { ImageRenderer* d = static_cast(user_data); d->sizePrepared(width, height); } } // extern "C"