/* -*- 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 GDI+ Renderer 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. * * Parts of this file contain code derived from the following files(s) * of the Mozilla SVG project (these parts are Copyright (C) by their * respective copyright-holders): * layout/svg/renderer/src/libart/nsSVGCairoGradient.cpp * * 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 #include #include using namespace Gdiplus; #include "nsCOMPtr.h" #include "nsIDOMSVGMatrix.h" #include "nsIDOMSVGNumber.h" #include "nsIDOMSVGLength.h" #include "nsIDOMSVGGradientElement.h" #include "nsIDOMSVGTransformList.h" #include "nsISVGPathGeometrySource.h" #include "nsSVGGDIPlusGradient.h" #include static Matrix * SVGToMatrix(nsIDOMSVGMatrix *ctm) { float A, B, C, D, E, F; ctm->GetA(&A); ctm->GetB(&B); ctm->GetC(&C); ctm->GetD(&D); ctm->GetE(&E); ctm->GetF(&F); return new Matrix(A, B, C, D, E, F); } static void GDIPlusGetStops(nsISVGGradient *aGrad, Color **aColors, REAL **aPositions, PRUint32 *aStops, PRBool aInvert) { PRUint32 num; aGrad->GetStopCount(&num); *aStops = num; // If we have no stops, we don't want to render anything if (num == 0) { return; } float offset; aGrad->GetStopOffset(0, &offset); if (offset != 0.0) *aStops += 1; aGrad->GetStopOffset(num-1, &offset); if (offset != 1.0) *aStops += 1; *aColors = new Color[*aStops]; *aPositions = new REAL[*aStops]; float lastOffset = 0.0f; for (PRUint32 i = 0, idx = 0; i < num; i++) { nscolor rgba; float offset; float opacity; aGrad->GetStopOffset(i, &offset); aGrad->GetStopColor(i, &rgba); aGrad->GetStopOpacity(i, &opacity); ARGB argb = Color::MakeARGB(BYTE(255*opacity), NS_GET_R(rgba), NS_GET_G(rgba), NS_GET_B(rgba)); if (idx == 0 && offset != 0.0) { (*aColors)[idx].SetValue(argb); (*aPositions)[idx] = (REAL)0.0f; idx++; } if (offset < lastOffset) offset = lastOffset; else lastOffset = offset; (*aColors)[idx].SetValue(argb); (*aPositions)[idx] = (REAL)offset; idx++; if (i == num-1 && offset != 1.0) { (*aColors)[idx].SetValue(argb); (*aPositions)[idx] = (REAL)1.0f; } } if (aInvert) { for (PRUint32 i=0; i < *aStops / 2; i++) { Color tmpColor; REAL tmpOffset; tmpColor = (*aColors)[i]; (*aColors)[i] = (*aColors)[*aStops - 1 - i]; (*aColors)[*aStops - 1 - i] = tmpColor; tmpOffset = (*aPositions)[i]; (*aPositions)[i] = 1.0f - (*aPositions)[*aStops - 1 - i]; (*aPositions)[*aStops - 1 - i] = 1.0f - tmpOffset; } // need to flip position of center color if (*aStops & 1) (*aPositions)[*aStops / 2] = 1.0f - (*aPositions)[*aStops / 2]; } } static void GDIPlusLinearGradient(nsISVGGradient *aGrad, Matrix *aMatrix, Graphics *aGFX, Matrix *aCTM, void(*aCallback)(Graphics *, Brush*, void *), void *aData) { float fX1, fY1, fX2, fY2; nsCOMPtraLgrad = do_QueryInterface(aGrad); NS_ASSERTION(aLgrad, "error gradient did not provide a Linear Gradient interface"); aLgrad->GetX1(&fX1); aLgrad->GetX2(&fX2); aLgrad->GetY1(&fY1); aLgrad->GetY2(&fY2); LinearGradientBrush gradient(PointF(fX1, fY1), PointF(fX2, fY2), Color(0xff, 0, 0), Color(0, 0xff, 0)); PRUint16 aSpread; aGrad->GetSpreadMethod(&aSpread); if (aSpread == nsIDOMSVGGradientElement::SVG_SPREADMETHOD_PAD) gradient.SetWrapMode(WrapModeTileFlipX); else if (aSpread == nsIDOMSVGGradientElement::SVG_SPREADMETHOD_REFLECT) gradient.SetWrapMode(WrapModeTileFlipX); else if (aSpread == nsIDOMSVGGradientElement::SVG_SPREADMETHOD_REPEAT) gradient.SetWrapMode(WrapModeTile); PRUint32 nStops; Color *stopsCol; REAL *stopsPos; GDIPlusGetStops(aGrad, &stopsCol, &stopsPos, &nStops, PR_FALSE); // If we have no stops, we don't want to render anything if (nStops == 0) { return; } gradient.SetInterpolationColors(stopsCol, stopsPos, nStops); SolidBrush leftBrush(stopsCol[0]); SolidBrush rightBrush(stopsCol[nStops-1]); delete [] stopsCol; delete [] stopsPos; gradient.MultiplyTransform(aMatrix, MatrixOrderAppend); gradient.MultiplyTransform(aCTM, MatrixOrderAppend); if (aSpread == nsIDOMSVGGradientElement::SVG_SPREADMETHOD_PAD) { float dx = fX2 - fX1; float dy = fY2 - fY1; // what we really want is a halfspace, but we don't have that... #define INF 100 PointF rect[4]; GraphicsPath left; left.StartFigure(); rect[0].X = fX1 + INF * dy; rect[0].Y = fY1 - INF * dx; rect[1].X = fX1 + INF * dx + INF * dy; rect[1].Y = fY1 + INF * dy - INF * dx; rect[2].X = fX1 + INF * dx - INF * dy; rect[2].Y = fY1 + INF * dy + INF * dx; rect[3].X = fX1 - INF * dy; rect[3].Y = fY1 + INF * dx; left.AddPolygon(rect, 4); left.Transform(aMatrix); left.Transform(aCTM); GraphicsPath center; center.StartFigure(); rect[0].X = fX1 + INF * dy; rect[0].Y = fY1 - INF * dx; rect[1].X = fX2 + INF * dy; rect[1].Y = fY2 - INF * dx; rect[2].X = fX2 - INF * dy; rect[2].Y = fY2 + INF * dx; rect[3].X = fX1 - INF * dy; rect[3].Y = fY1 + INF * dx; center.AddPolygon(rect, 4); center.Transform(aMatrix); center.Transform(aCTM); GraphicsPath right; right.StartFigure(); rect[0].X = fX2 - INF * dx + INF * dy; rect[0].Y = fY2 - INF * dy - INF * dx; rect[1].X = fX2 + INF * dy; rect[1].Y = fY2 - INF * dx; rect[2].X = fX2 - INF * dy; rect[2].Y = fY2 + INF * dx; rect[3].X = fX2 - INF * dx - INF * dy; rect[3].Y = fY2 - INF * dy + INF * dx; right.AddPolygon(rect, 4); right.Transform(aMatrix); right.Transform(aCTM); Region leftRegion(&left), centerRegion(¢er), rightRegion(&right), oldClip; aGFX->GetClip(&oldClip); aGFX->SetClip(&leftRegion, CombineModeExclude); aCallback(aGFX, &leftBrush, aData); aGFX->SetClip(&oldClip); aGFX->SetClip(¢erRegion, CombineModeIntersect); aCallback(aGFX, &gradient, aData); aGFX->SetClip(&oldClip); aGFX->SetClip(&rightRegion, CombineModeExclude); aCallback(aGFX, &rightBrush, aData); aGFX->SetClip(&oldClip); } else { aCallback(aGFX, &gradient, aData); } } static void GDIPlusRadialGradient(nsISVGGradient *aGrad, Matrix *aMatrix, Graphics *aGFX, Matrix *aCTM, void(*aCallback)(Graphics *, Brush*, void *), void *aData) { float fCx, fCy, fR, fFx, fFy; // Get the Radial Gradient interface nsCOMPtraRgrad = do_QueryInterface(aGrad); NS_ASSERTION(aRgrad, "error gradient did not provide a Linear Gradient interface"); aRgrad->GetCx(&fCx); aRgrad->GetCy(&fCy); aRgrad->GetR(&fR); aRgrad->GetFx(&fFx); aRgrad->GetFy(&fFy); GraphicsPath circle; circle.StartFigure(); circle.AddEllipse(fCx - fR, fCy - fR, 2 * fR, 2 * fR); PathGradientBrush gradient(&circle); gradient.SetCenterPoint(PointF(fFx, fFy)); PRUint16 aSpread; aGrad->GetSpreadMethod(&aSpread); if (aSpread == nsIDOMSVGGradientElement::SVG_SPREADMETHOD_PAD) { gradient.SetWrapMode(WrapModeClamp); } else if (aSpread == nsIDOMSVGGradientElement::SVG_SPREADMETHOD_REFLECT) gradient.SetWrapMode(WrapModeTileFlipX); else if (aSpread == nsIDOMSVGGradientElement::SVG_SPREADMETHOD_REPEAT) gradient.SetWrapMode(WrapModeTile); PRUint32 nStops; Color *stopsCol; REAL *stopsPos; GDIPlusGetStops(aGrad, &stopsCol, &stopsPos, &nStops, PR_TRUE); // If we have no stops, we don't want to render anything if (nStops == 0) { return; } gradient.SetInterpolationColors(stopsCol, stopsPos, nStops); SolidBrush rimBrush(stopsCol[0]); delete [] stopsCol; delete [] stopsPos; gradient.MultiplyTransform(aMatrix, MatrixOrderAppend); gradient.MultiplyTransform(aCTM, MatrixOrderAppend); if (aSpread == nsIDOMSVGGradientElement::SVG_SPREADMETHOD_PAD) { circle.Transform(aMatrix); circle.Transform(aCTM); Region exclude(&circle), oldClip; aGFX->GetClip(&oldClip); aGFX->SetClip(&exclude, CombineModeExclude); aCallback(aGFX, &rimBrush, aData); aGFX->SetClip(&oldClip); } aCallback(aGFX, &gradient, aData); } void GDIPlusGradient(nsISVGGDIPlusRegion *aRegion, nsISVGGradient *aGrad, nsIDOMSVGMatrix *aCTM, Graphics *aGFX, nsISVGGeometrySource *aSource, void(*aCallback)(Graphics *, Brush*, void *), void *aData) { NS_ASSERTION(aGrad, "Called GDIPlusGradient without a gradient!"); if (!aGrad) return; // Get the gradientUnits PRUint16 bbox; aGrad->GetGradientUnits(&bbox); // Get the transform list (if there is one) nsCOMPtr svgMatrix; aGrad->GetGradientTransform(getter_AddRefs(svgMatrix), aSource); NS_ASSERTION(svgMatrix, "GDIPlusGradient: GetGradientTransform returns null"); // GDI+ gradients don't like having a zero on the diagonal (zero // width or height in the bounding box) float val; svgMatrix->GetA(&val); if (val == 0.0) svgMatrix->SetA(1.0); svgMatrix->GetD(&val); if (val == 0.0) svgMatrix->SetD(1.0); Matrix *patternMatrix = SVGToMatrix(svgMatrix); Matrix *ctm = SVGToMatrix(aCTM); // Linear or Radial? PRUint32 type; aGrad->GetGradientType(&type); if (type == nsISVGGradient::SVG_LINEAR_GRADIENT) GDIPlusLinearGradient(aGrad, patternMatrix, aGFX, ctm, aCallback, aData); else if (type == nsISVGGradient::SVG_RADIAL_GRADIENT) GDIPlusRadialGradient(aGrad, patternMatrix, aGFX, ctm, aCallback, aData); delete patternMatrix; delete ctm; }