/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the Mozilla SVG project. * * The Initial Developer of the Original Code is IBM Corporation. * Portions created by the Initial Developer are Copyright (C) 2004 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsSVGPathGeometryFrame.h" #include "nsISVGRendererCanvas.h" #include "nsISVGRenderer.h" #include "nsIDOMSVGMatrix.h" #include "nsIDOMSVGAnimPresAspRatio.h" #include "nsIDOMSVGPresAspectRatio.h" #include "imgIContainer.h" #include "gfxIImageFrame.h" #include "nsStubImageDecoderObserver.h" #include "nsImageLoadingContent.h" #include "nsIDOMSVGImageElement.h" #include "nsSVGElement.h" #include "nsSVGUtils.h" #include "nsIImage.h" /* for MOZ_PLATFORM_IMAGES_BOTTOM_TO_TOP */ #include "nsSVGMatrix.h" #include "nsISVGCairoCanvas.h" #include "cairo.h" #define NS_GET_BIT(rowptr, x) (rowptr[(x)>>3] & (1<<(7-(x)&0x7))) class nsSVGImageFrame; class nsSVGImageListener : public nsStubImageDecoderObserver { public: nsSVGImageListener(nsSVGImageFrame *aFrame); virtual ~nsSVGImageListener(); NS_DECL_ISUPPORTS // imgIDecoderObserver (override nsStubImageDecoderObserver) NS_IMETHOD OnStopDecode(imgIRequest *aRequest, nsresult status, const PRUnichar *statusArg); // imgIContainerObserver (override nsStubImageDecoderObserver) NS_IMETHOD FrameChanged(imgIContainer *aContainer, gfxIImageFrame *newframe, nsRect * dirtyRect); void SetFrame(nsSVGImageFrame *frame) { mFrame = frame; } private: nsSVGImageFrame *mFrame; }; class nsSVGImageFrame : public nsSVGPathGeometryFrame { protected: friend nsIFrame* NS_NewSVGImageFrame(nsIPresShell* aPresShell, nsIContent* aContent, nsStyleContext* aContext); virtual ~nsSVGImageFrame(); NS_IMETHOD InitSVG(); public: nsSVGImageFrame(nsStyleContext* aContext) : nsSVGPathGeometryFrame(aContext) {} // nsISVGChildFrame interface: NS_IMETHOD PaintSVG(nsISVGRendererCanvas* canvas, nsRect *aDirtyRect); NS_IMETHOD GetFrameForPointSVG(float x, float y, nsIFrame** hit); // nsSVGGeometryFrame overload: // Lie about our fill/stroke so hit detection works virtual nsresult GetStrokePaintType() { return eStyleSVGPaintType_None; } virtual nsresult GetFillPaintType() { return eStyleSVGPaintType_Color; } // nsSVGPathGeometryFrame methods: virtual PRUint16 GetHittestMask(); // nsIFrame interface: NS_IMETHOD AttributeChanged(PRInt32 aNameSpaceID, nsIAtom* aAttribute, PRInt32 aModType); /** * Get the "type" of the frame * * @see nsLayoutAtoms::svgImageFrame */ virtual nsIAtom* GetType() const; #ifdef DEBUG NS_IMETHOD GetFrameName(nsAString& aResult) const { return MakeFrameName(NS_LITERAL_STRING("SVGImage"), aResult); } #endif private: already_AddRefed GetImageTransform(); nsCOMPtr mPreserveAspectRatio; nsCOMPtr mListener; cairo_surface_t *mSurface; nsresult ConvertFrame(gfxIImageFrame *aNewFrame); friend class nsSVGImageListener; PRPackedBool mSurfaceInvalid; }; //---------------------------------------------------------------------- // Implementation nsIFrame* NS_NewSVGImageFrame(nsIPresShell* aPresShell, nsIContent* aContent, nsStyleContext* aContext) { nsCOMPtr Rect = do_QueryInterface(aContent); if (!Rect) { #ifdef DEBUG printf("warning: trying to construct an SVGImageFrame for a content element that doesn't support the right interfaces\n"); #endif return nsnull; } return new (aPresShell) nsSVGImageFrame(aContext); } nsSVGImageFrame::~nsSVGImageFrame() { // set the frame to null so we don't send messages to a dead object. if (mListener) { nsCOMPtr imageLoader = do_QueryInterface(mContent); if (imageLoader) { imageLoader->RemoveObserver(mListener); } NS_REINTERPRET_CAST(nsSVGImageListener*, mListener.get())->SetFrame(nsnull); } mListener = nsnull; if (mSurface) cairo_surface_destroy(mSurface); } NS_IMETHODIMP nsSVGImageFrame::InitSVG() { nsresult rv = nsSVGPathGeometryFrame::InitSVG(); if (NS_FAILED(rv)) return rv; nsCOMPtr Rect = do_QueryInterface(mContent); NS_ASSERTION(Rect,"wrong content element"); { nsCOMPtr ratio; Rect->GetPreserveAspectRatio(getter_AddRefs(ratio)); ratio->GetAnimVal(getter_AddRefs(mPreserveAspectRatio)); NS_ASSERTION(mPreserveAspectRatio, "no preserveAspectRatio"); if (!mPreserveAspectRatio) return NS_ERROR_FAILURE; } mSurface = nsnull; mSurfaceInvalid = PR_TRUE; mListener = new nsSVGImageListener(this); if (!mListener) return NS_ERROR_OUT_OF_MEMORY; nsCOMPtr imageLoader = do_QueryInterface(mContent); NS_ENSURE_TRUE(imageLoader, NS_ERROR_UNEXPECTED); imageLoader->AddObserver(mListener); return NS_OK; } //---------------------------------------------------------------------- // nsIFrame methods: NS_IMETHODIMP nsSVGImageFrame::AttributeChanged(PRInt32 aNameSpaceID, nsIAtom* aAttribute, PRInt32 aModType) { if (aNameSpaceID == kNameSpaceID_None && (aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y || aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height || aAttribute == nsGkAtoms::preserveAspectRatio)) { UpdateGraphic(); return NS_OK; } return nsSVGPathGeometryFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); } already_AddRefed nsSVGImageFrame::GetImageTransform() { nsCOMPtr ctm; GetCanvasTM(getter_AddRefs(ctm)); float x, y, width, height; nsSVGElement *element = NS_STATIC_CAST(nsSVGElement*, mContent); element->GetAnimatedLengthValues(&x, &y, &width, &height, nsnull); PRUint32 nativeWidth, nativeHeight; nativeWidth = cairo_image_surface_get_width(mSurface); nativeHeight = cairo_image_surface_get_height(mSurface); nsCOMPtr image = do_QueryInterface(mContent); nsCOMPtr ratio; image->GetPreserveAspectRatio(getter_AddRefs(ratio)); nsCOMPtr trans, ctmXY, fini; trans = nsSVGUtils::GetViewBoxTransform(width, height, 0, 0, nativeWidth, nativeHeight, ratio); ctm->Translate(x, y, getter_AddRefs(ctmXY)); ctmXY->Multiply(trans, getter_AddRefs(fini)); nsIDOMSVGMatrix *retval = nsnull; fini.swap(retval); return retval; } //---------------------------------------------------------------------- // nsISVGChildFrame methods: NS_IMETHODIMP nsSVGImageFrame::PaintSVG(nsISVGRendererCanvas* canvas, nsRect *aDirtyRect) { if (!GetStyleVisibility()->IsVisible()) return NS_OK; if (mSurfaceInvalid) { nsCOMPtr currentRequest; nsCOMPtr imageLoader = do_QueryInterface(mContent); if (imageLoader) imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(currentRequest)); nsCOMPtr currentContainer; if (currentRequest) currentRequest->GetImage(getter_AddRefs(currentContainer)); nsCOMPtr currentFrame; if (currentContainer) currentContainer->GetCurrentFrame(getter_AddRefs(currentFrame)); if (currentFrame) { ConvertFrame(currentFrame); mSurfaceInvalid = PR_FALSE; } else { return NS_OK; } } if (mSurface) { nsCOMPtr ctm; GetCanvasTM(getter_AddRefs(ctm)); float x, y, width, height; nsSVGElement *element = NS_STATIC_CAST(nsSVGElement*, mContent); element->GetAnimatedLengthValues(&x, &y, &width, &height, nsnull); nsCOMPtr fini = GetImageTransform(); if (GetStyleDisplay()->IsScrollableOverflow()) { canvas->PushClip(); canvas->SetClipRect(ctm, x, y, width, height); } canvas->CompositeSurfaceMatrix(mSurface, fini, mStyleContext->GetStyleDisplay()->mOpacity); if (GetStyleDisplay()->IsScrollableOverflow()) canvas->PopClip(); } return NS_OK; } NS_IMETHODIMP nsSVGImageFrame::GetFrameForPointSVG(float x, float y, nsIFrame** hit) { if (GetStyleDisplay()->IsScrollableOverflow() && mSurface) { PRUint32 nativeWidth, nativeHeight; nativeWidth = cairo_image_surface_get_width(mSurface); nativeHeight = cairo_image_surface_get_height(mSurface); nsCOMPtr fini = GetImageTransform(); if (!nsSVGUtils::HitTestRect(fini, 0, 0, nativeWidth, nativeHeight, x, y)) { *hit = nsnull; return NS_OK; } } return nsSVGPathGeometryFrame::GetFrameForPointSVG(x, y, hit); } nsIAtom * nsSVGImageFrame::GetType() const { return nsLayoutAtoms::svgImageFrame; } nsresult nsSVGImageFrame::ConvertFrame(gfxIImageFrame *aNewFrame) { PRInt32 width, height; aNewFrame->GetWidth(&width); aNewFrame->GetHeight(&height); mSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); if (!mSurface) return NS_ERROR_FAILURE; PRUint8 *data, *target; PRUint32 length; PRInt32 stride; data = cairo_image_surface_get_data(mSurface); stride = cairo_image_surface_get_stride(mSurface); #ifdef MOZ_PLATFORM_IMAGES_BOTTOM_TO_TOP stride = -stride; #endif aNewFrame->LockImageData(); aNewFrame->LockAlphaData(); PRUint8 *rgb, *alpha = nsnull; PRUint32 bpr, abpr; aNewFrame->GetImageData(&rgb, &length); aNewFrame->GetImageBytesPerRow(&bpr); if (!rgb) { aNewFrame->UnlockImageData(); aNewFrame->UnlockAlphaData(); return NS_ERROR_FAILURE; } #ifdef MOZ_CAIRO_GFX // cairo gfx already has the data in the order/format - just copy memcpy(data, rgb, bpr*height); #else aNewFrame->GetAlphaData(&alpha, &length); aNewFrame->GetAlphaBytesPerRow(&abpr); // some platforms return 4bpp (OSX and Win32 under some circumstances) const PRUint32 bpp = bpr/width; #ifdef XP_MACOSX // pixels on os-x have a lead byte we don't care about (alpha or // garbage, depending on the image format) - shift our pointer down // one so we can use the rest of the code as-is rgb++; #endif #if (defined(XP_UNIX) && !defined(XP_MACOSX)) || defined(MOZ_CAIRO_GFX) #define REVERSE_CHANNELS #endif #if defined(XP_MACOSX) && defined(__i386__) #define REVERSE_CHANNELS #endif // cairo/os-x wants ABGR format, GDI+ wants RGBA, cairo/unix wants BGRA if (!alpha) { for (PRInt32 y=0; y 0) target = data + stride * y; else target = data + stride * (1 - height) + stride * y; for (PRInt32 x=0; x= width) { /* 8-bit alpha */ for (PRInt32 y=0; y 0) target = data + stride * y; else target = data + stride * (1 - height) + stride * y; for (PRInt32 x=0; x 0) target = data + stride * y; else target = data + stride * (1 - height) + stride * y; PRUint8 *alphaRow = alpha + y*abpr; for (PRUint32 x=0; xUnlockImageData(); aNewFrame->UnlockAlphaData(); return NS_OK; } //---------------------------------------------------------------------- // nsSVGPathGeometryFrame methods: // Lie about our fill/stroke so that hit detection works properly PRUint16 nsSVGImageFrame::GetHittestMask() { PRUint16 mask = 0; switch(GetStyleSVG()->mPointerEvents) { case NS_STYLE_POINTER_EVENTS_NONE: break; case NS_STYLE_POINTER_EVENTS_VISIBLEPAINTED: if (GetStyleVisibility()->IsVisible()) { /* XXX: should check pixel transparency */ mask |= HITTEST_MASK_FILL; } break; case NS_STYLE_POINTER_EVENTS_VISIBLEFILL: case NS_STYLE_POINTER_EVENTS_VISIBLESTROKE: case NS_STYLE_POINTER_EVENTS_VISIBLE: if (GetStyleVisibility()->IsVisible()) { mask |= HITTEST_MASK_FILL; } break; case NS_STYLE_POINTER_EVENTS_PAINTED: /* XXX: should check pixel transparency */ mask |= HITTEST_MASK_FILL; break; case NS_STYLE_POINTER_EVENTS_FILL: case NS_STYLE_POINTER_EVENTS_STROKE: case NS_STYLE_POINTER_EVENTS_ALL: mask |= HITTEST_MASK_FILL; break; default: NS_ERROR("not reached"); break; } return mask; } //---------------------------------------------------------------------- // nsSVGImageListener implementation NS_IMPL_ISUPPORTS2(nsSVGImageListener, imgIDecoderObserver, imgIContainerObserver) nsSVGImageListener::nsSVGImageListener(nsSVGImageFrame *aFrame) : mFrame(aFrame) { } nsSVGImageListener::~nsSVGImageListener() { } NS_IMETHODIMP nsSVGImageListener::OnStopDecode(imgIRequest *aRequest, nsresult status, const PRUnichar *statusArg) { if (!mFrame) return NS_ERROR_FAILURE; mFrame->mSurfaceInvalid = PR_TRUE; mFrame->UpdateGraphic(); return NS_OK; } NS_IMETHODIMP nsSVGImageListener::FrameChanged(imgIContainer *aContainer, gfxIImageFrame *newframe, nsRect * dirtyRect) { if (!mFrame) return NS_ERROR_FAILURE; mFrame->mSurfaceInvalid = PR_TRUE; mFrame->UpdateGraphic(); return NS_OK; }