/**
 * Try the direct path.
 * @return True if we took the direct path
 */
bool
gfxXlibNativeRenderer::DrawDirect(gfxContext *ctx, nsIntSize size,
                                  PRUint32 flags,
                                  Screen *screen, Visual *visual)
{
    cairo_t *cr = ctx->GetCairo();

    /* Check that the target surface is an xlib surface. */
    cairo_surface_t *target = cairo_get_group_target (cr);
    if (cairo_surface_get_type (target) != CAIRO_SURFACE_TYPE_XLIB) {
        NATIVE_DRAWING_NOTE("FALLBACK: non-X surface");
        return false;
    }
    
    cairo_matrix_t matrix;
    cairo_get_matrix (cr, &matrix);
    double device_offset_x, device_offset_y;
    cairo_surface_get_device_offset (target, &device_offset_x, &device_offset_y);

    /* Draw() checked that the matrix contained only a very-close-to-integer
       translation.  Here (and in several other places and thebes) device
       offsets are assumed to be integer. */
    NS_ASSERTION(PRInt32(device_offset_x) == device_offset_x &&
                 PRInt32(device_offset_y) == device_offset_y,
                 "Expected integer device offsets");
    nsIntPoint offset(NS_lroundf(matrix.x0 + device_offset_x),
                      NS_lroundf(matrix.y0 + device_offset_y));
    
    int max_rectangles = 0;
    if (flags & DRAW_SUPPORTS_CLIP_RECT) {
      max_rectangles = 1;
    }
    if (flags & DRAW_SUPPORTS_CLIP_LIST) {
      max_rectangles = MAX_STATIC_CLIP_RECTANGLES;
    }

    /* The client won't draw outside the surface so consider this when
       analysing clip rectangles. */
    nsIntRect bounds(offset, size);
    bounds.IntersectRect(bounds,
                         nsIntRect(0, 0,
                                   cairo_xlib_surface_get_width(target),
                                   cairo_xlib_surface_get_height(target)));

    bool needs_clip = true;
    nsIntRect rectangles[MAX_STATIC_CLIP_RECTANGLES];
    int rect_count = 0;

    /* Check that the clip is rectangular and aligned on unit boundaries. */
    /* Temporarily set the matrix for _get_rectangular_clip. It's basically
       the identity matrix, but we must adjust for the fact that our
       offset-rect is in device coordinates. */
    cairo_identity_matrix (cr);
    cairo_translate (cr, -device_offset_x, -device_offset_y);
    bool have_rectangular_clip =
        _get_rectangular_clip (cr, bounds, &needs_clip,
                               rectangles, max_rectangles, &rect_count);
    cairo_set_matrix (cr, &matrix);
    if (!have_rectangular_clip)
        return false;

    /* Stop now if everything is clipped out */
    if (needs_clip && rect_count == 0)
        return true;
      
    /* Check that the screen is supported.
       Visuals belong to screens, so, if alternate visuals are not supported,
       then alternate screens cannot be supported. */  
    bool supports_alternate_visual =
        (flags & DRAW_SUPPORTS_ALTERNATE_VISUAL) != 0;
    bool supports_alternate_screen = supports_alternate_visual &&
        (flags & DRAW_SUPPORTS_ALTERNATE_SCREEN);
    if (!supports_alternate_screen &&
        cairo_xlib_surface_get_screen (target) != screen) {
        NATIVE_DRAWING_NOTE("FALLBACK: non-default screen");
        return false;
    }
        
    /* Check that there is a visual */
    Visual *target_visual = cairo_xlib_surface_get_visual (target);
    if (!target_visual) {
        NATIVE_DRAWING_NOTE("FALLBACK: no Visual for surface");
        return false;
    }        
    /* Check that the visual is supported */
    if (!supports_alternate_visual && target_visual != visual) {
        // Only the format of the visual is important (not the GLX properties)
        // for Xlib or XRender drawing.
        XRenderPictFormat *target_format =
            cairo_xlib_surface_get_xrender_format (target);
        if (!target_format ||
            (target_format !=
             XRenderFindVisualFormat (DisplayOfScreen(screen), visual))) {
            NATIVE_DRAWING_NOTE("FALLBACK: unsupported Visual");
            return false;
        }
    }
  
    /* we're good to go! */
    NATIVE_DRAWING_NOTE("TAKING FAST PATH\n");
    cairo_surface_flush (target);
    nsRefPtr<gfxASurface> surface = gfxASurface::Wrap(target);
    nsresult rv = DrawWithXlib(static_cast<gfxXlibSurface*>(surface.get()),
                               offset, rectangles,
                               needs_clip ? rect_count : 0);
    if (NS_SUCCEEDED(rv)) {
        cairo_surface_mark_dirty (target);
        return true;
    }
    return false;
}
/* NB: These are pixels, not twips. */
NS_IMETHODIMP
nsThebesImage::Draw(nsIRenderingContext &aContext,
                    const gfxRect &aSourceRect,
                    const gfxRect &aSubimageRect,
                    const gfxRect &aDestRect)
{
    if (NS_UNLIKELY(aDestRect.IsEmpty())) {
        NS_ERROR("nsThebesImage::Draw zero dest size - please fix caller.");
        return NS_OK;
    }

    nsThebesRenderingContext *thebesRC = static_cast<nsThebesRenderingContext*>(&aContext);
    gfxContext *ctx = thebesRC->ThebesContext();

#if 0
    fprintf (stderr, "nsThebesImage::Draw src [%f %f %f %f] dest [%f %f %f %f] trans: [%f %f] dec: [%f %f]\n",
             aSourceRect.pos.x, aSourceRect.pos.y, aSourceRect.size.width, aSourceRect.size.height,
             aDestRect.pos.x, aDestRect.pos.y, aDestRect.size.width, aDestRect.size.height,
             ctx->CurrentMatrix().GetTranslation().x, ctx->CurrentMatrix().GetTranslation().y,
             mDecoded.x, mDecoded.y, mDecoded.width, mDecoded.height);
#endif

    if (mSinglePixel) {
        // if a == 0, it's a noop
        if (mSinglePixelColor.a == 0.0)
            return NS_OK;

        // otherwise
        gfxContext::GraphicsOperator op = ctx->CurrentOperator();
        if (op == gfxContext::OPERATOR_OVER && mSinglePixelColor.a == 1.0)
            ctx->SetOperator(gfxContext::OPERATOR_SOURCE);

        ctx->SetDeviceColor(mSinglePixelColor);
        ctx->NewPath();
        ctx->Rectangle(aDestRect, PR_TRUE);
        ctx->Fill();
        ctx->SetOperator(op);
        return NS_OK;
    }

    gfxFloat xscale = aDestRect.size.width / aSourceRect.size.width;
    gfxFloat yscale = aDestRect.size.height / aSourceRect.size.height;

    gfxRect srcRect(aSourceRect);
    gfxRect subimageRect(aSubimageRect);
    gfxRect destRect(aDestRect);

    if (!GetIsImageComplete()) {
        gfxRect decoded = gfxRect(mDecoded.x, mDecoded.y,
                                  mDecoded.width, mDecoded.height);
        srcRect = srcRect.Intersect(decoded);
        subimageRect = subimageRect.Intersect(decoded);

        // This happens when mDecoded.width or height is zero. bug 368427.
        if (NS_UNLIKELY(srcRect.size.width == 0 || srcRect.size.height == 0))
            return NS_OK;

        destRect.pos.x += (srcRect.pos.x - aSourceRect.pos.x)*xscale;
        destRect.pos.y += (srcRect.pos.y - aSourceRect.pos.y)*yscale;

        destRect.size.width  = srcRect.size.width * xscale;
        destRect.size.height = srcRect.size.height * yscale;
    }

    // if either rectangle is empty now (possibly after the image complete check)
    if (srcRect.IsEmpty() || destRect.IsEmpty())
        return NS_OK;

    // Reject over-wide or over-tall images.
    if (!AllowedImageSize(destRect.size.width + 1, destRect.size.height + 1))
        return NS_ERROR_FAILURE;

    // Expand the subimageRect to place its edges on integer coordinates.
    // Basically, if we're allowed to sample part of a pixel we can
    // sample the whole pixel.
    subimageRect.RoundOut();

    nsRefPtr<gfxPattern> pat;
    PRBool ctxHasNonTranslation = ctx->CurrentMatrix().HasNonTranslation();
    if ((xscale == 1.0 && yscale == 1.0 && !ctxHasNonTranslation) ||
        subimageRect == gfxRect(0, 0, mWidth, mHeight))
    {
        // No need to worry about sampling outside the subimage rectangle,
        // so no need for a temporary
        // XXX should we also check for situations where the source rect
        // is well inside the subimage so we can't sample outside?
        pat = new gfxPattern(ThebesSurface());
    } else {
        // Because of the RoundOut above, the subimageRect has
        // integer width and height.
        gfxIntSize size(PRInt32(subimageRect.Width()),
                        PRInt32(subimageRect.Height()));
        nsRefPtr<gfxASurface> temp =
            gfxPlatform::GetPlatform()->CreateOffscreenSurface(size, mFormat);
        if (!temp || temp->CairoStatus() != 0)
            return NS_ERROR_FAILURE;

        gfxContext tempctx(temp);
        tempctx.SetSource(ThebesSurface(), -subimageRect.pos);
        tempctx.SetOperator(gfxContext::OPERATOR_SOURCE);
        tempctx.Paint();

        pat = new gfxPattern(temp);
        srcRect.MoveBy(-subimageRect.pos);
    }

    /* See bug 364968 to understand the necessity of this goop; we basically
     * have to pre-downscale any image that would fall outside of a scaled 16-bit
     * coordinate space.
     */
    gfxFloat deviceX, deviceY;
    nsRefPtr<gfxASurface> currentTarget =
	    ctx->CurrentSurface(&deviceX, &deviceY);

    // Quartz's matrix limits are much larger than pixman so don't use a temporary
    // context there so we preserve scaled image caching
    if (currentTarget->GetType() != gfxASurface::SurfaceTypeQuartz &&
       (aDestRect.pos.x * (1.0 / xscale) >= 32768.0 ||
        aDestRect.pos.y * (1.0 / yscale) >= 32768.0))
    {
        gfxIntSize dim(NS_lroundf(destRect.size.width),
                       NS_lroundf(destRect.size.height));

        // nothing to do in this case
        if (dim.width == 0 || dim.height == 0)
            return NS_OK;

        nsRefPtr<gfxASurface> temp =
            gfxPlatform::GetPlatform()->CreateOffscreenSurface (dim,  mFormat);
        if (!temp || temp->CairoStatus() != 0)
            return NS_ERROR_FAILURE;

        gfxContext tempctx(temp);

        gfxMatrix mat;
        mat.Translate(srcRect.pos);
        mat.Scale(1.0 / xscale, 1.0 / yscale);
        pat->SetMatrix(mat);

        tempctx.SetPattern(pat);
        tempctx.SetOperator(gfxContext::OPERATOR_SOURCE);
        tempctx.NewPath();
        tempctx.Rectangle(gfxRect(0.0, 0.0, dim.width, dim.height));
        tempctx.Fill();

        pat = new gfxPattern(temp);

        srcRect.pos.x = 0.0;
        srcRect.pos.y = 0.0;
        srcRect.size.width = dim.width;
        srcRect.size.height = dim.height;

        xscale = 1.0;
        yscale = 1.0;
    }

    gfxMatrix mat;
    mat.Translate(srcRect.pos);
    mat.Scale(1.0/xscale, 1.0/yscale);

    /* Translate the start point of the image (srcRect.pos)
     * to coincide with the destination rectangle origin
     */
    mat.Translate(-destRect.pos);

    pat->SetMatrix(mat);

    nsRefPtr<gfxASurface> target = ctx->CurrentSurface();
    switch (target->GetType()) {
    case gfxASurface::SurfaceTypeXlib:
    case gfxASurface::SurfaceTypeXcb:
        // See bug 324698.  This is a workaround for EXTEND_PAD not being
        // implemented correctly on linux in the X server.
        //
        // Set the filter to CAIRO_FILTER_FAST if we're scaling up -- otherwise,
        // pixman's sampling will sample transparency for the outside edges and we'll
        // get blurry edges.  CAIRO_EXTEND_PAD would also work here, if
        // available
        //
        // This effectively disables smooth upscaling for images.
        if (xscale > 1.0 || yscale > 1.0 || ctxHasNonTranslation)
            pat->SetFilter(0);
        break;

    case gfxASurface::SurfaceTypeQuartz:
    case gfxASurface::SurfaceTypeQuartzImage:
        // Do nothing, Mac seems to be OK. Really?
        break;

    default:
        // turn on EXTEND_PAD.
        // This is what we really want for all surface types, if the
        // implementation was universally good.
        if (xscale != 1.0 || yscale != 1.0 || ctxHasNonTranslation)
            pat->SetExtend(gfxPattern::EXTEND_PAD);
        break;
    }

    gfxContext::GraphicsOperator op = ctx->CurrentOperator();
    if (op == gfxContext::OPERATOR_OVER && mFormat == gfxASurface::ImageFormatRGB24)
        ctx->SetOperator(gfxContext::OPERATOR_SOURCE);

    ctx->NewPath();
    ctx->SetPattern(pat);
    ctx->Rectangle(destRect);
    ctx->Fill();

    ctx->SetOperator(op);
    ctx->SetDeviceColor(gfxRGBA(0,0,0,0));

    return NS_OK;
}