Back to index

lightning-sunbird  0.9+nobinonly
nsSVGGDIPlusGradient.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is the Mozilla SVG GDI+ Renderer project.
00016  *
00017  * The Initial Developer of the Original Code is IBM Corporation.
00018  * Portions created by the Initial Developer are Copyright (C) 2004
00019  * the Initial Developer. All Rights Reserved.
00020  *
00021  * Parts of this file contain code derived from the following files(s)
00022  * of the Mozilla SVG project (these parts are Copyright (C) by their
00023  * respective copyright-holders):
00024  *    layout/svg/renderer/src/libart/nsSVGCairoGradient.cpp
00025  *
00026  * Contributor(s):
00027  *
00028  * Alternatively, the contents of this file may be used under the terms of
00029  * either of the GNU General Public License Version 2 or later (the "GPL"),
00030  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00031  * in which case the provisions of the GPL or the LGPL are applicable instead
00032  * of those above. If you wish to allow use of your version of this file only
00033  * under the terms of either the GPL or the LGPL, and not to allow others to
00034  * use your version of this file under the terms of the MPL, indicate your
00035  * decision by deleting the provisions above and replace them with the notice
00036  * and other provisions required by the GPL or the LGPL. If you do not delete
00037  * the provisions above, a recipient may use your version of this file under
00038  * the terms of any one of the MPL, the GPL or the LGPL.
00039  *
00040  * ***** END LICENSE BLOCK ***** */
00041 
00042 #include <windows.h>
00043 #include <unknwn.h>
00044 #include <Gdiplus.h>
00045 using namespace Gdiplus;
00046 
00047 #include "nsCOMPtr.h"
00048 #include "nsIDOMSVGMatrix.h"
00049 #include "nsIDOMSVGNumber.h"
00050 #include "nsIDOMSVGLength.h"
00051 #include "nsIDOMSVGGradientElement.h"
00052 #include "nsIDOMSVGTransformList.h"
00053 #include "nsISVGPathGeometrySource.h"
00054 #include "nsSVGGDIPlusGradient.h"
00055 #include <stdio.h>
00056 
00057 static Matrix *
00058 SVGToMatrix(nsIDOMSVGMatrix *ctm)
00059 {
00060   float A, B, C, D, E, F;
00061   ctm->GetA(&A);
00062   ctm->GetB(&B);
00063   ctm->GetC(&C);
00064   ctm->GetD(&D);
00065   ctm->GetE(&E);
00066   ctm->GetF(&F);
00067   return new Matrix(A, B, C, D, E, F);
00068 }
00069 
00070 static void
00071 GDIPlusGetStops(nsISVGGradient *aGrad, Color **aColors, REAL **aPositions,
00072                 PRUint32 *aStops, PRBool aInvert)
00073 {
00074   PRUint32 num;
00075   aGrad->GetStopCount(&num);
00076   *aStops = num;
00077   // If we have no stops, we don't want to render anything
00078   if (num == 0) {
00079     return;
00080   }
00081 
00082   float offset;
00083   aGrad->GetStopOffset(0, &offset);
00084   if (offset != 0.0)
00085     *aStops += 1;
00086   aGrad->GetStopOffset(num-1, &offset);
00087   if (offset != 1.0)
00088     *aStops += 1;
00089 
00090   *aColors = new Color[*aStops];
00091   *aPositions = new REAL[*aStops];
00092 
00093   float lastOffset = 0.0f;
00094   for (PRUint32 i = 0, idx = 0; i < num; i++) {
00095     nscolor rgba;
00096     float offset;
00097     float opacity;
00098 
00099     aGrad->GetStopOffset(i, &offset);
00100     aGrad->GetStopColor(i, &rgba);
00101     aGrad->GetStopOpacity(i, &opacity);
00102     ARGB argb = Color::MakeARGB(BYTE(255*opacity),
00103                                 NS_GET_R(rgba),
00104                                 NS_GET_G(rgba),
00105                                 NS_GET_B(rgba));
00106     if (idx == 0 && offset != 0.0) {
00107       (*aColors)[idx].SetValue(argb);
00108       (*aPositions)[idx] = (REAL)0.0f;
00109       idx++;
00110     }
00111 
00112     if (offset < lastOffset)
00113       offset = lastOffset;
00114     else
00115       lastOffset = offset;
00116 
00117     (*aColors)[idx].SetValue(argb);
00118     (*aPositions)[idx] = (REAL)offset;
00119     idx++;
00120 
00121     if (i == num-1 && offset != 1.0) {
00122       (*aColors)[idx].SetValue(argb);
00123       (*aPositions)[idx] = (REAL)1.0f;
00124     }
00125   }
00126 
00127   if (aInvert) {
00128     for (PRUint32 i=0; i < *aStops / 2; i++) {
00129       Color tmpColor;
00130       REAL tmpOffset;
00131 
00132       tmpColor = (*aColors)[i];
00133       (*aColors)[i] = (*aColors)[*aStops - 1 - i];
00134       (*aColors)[*aStops - 1 - i] = tmpColor;
00135 
00136       tmpOffset = (*aPositions)[i];
00137       (*aPositions)[i] = 1.0f - (*aPositions)[*aStops - 1 - i];
00138       (*aPositions)[*aStops - 1 - i] = 1.0f - tmpOffset;
00139     }
00140     // need to flip position of center color
00141     if (*aStops & 1)
00142       (*aPositions)[*aStops / 2] = 1.0f - (*aPositions)[*aStops / 2];
00143   }
00144 }
00145 
00146 static void
00147 GDIPlusLinearGradient(nsISVGGradient *aGrad, Matrix *aMatrix,
00148                       Graphics *aGFX, Matrix *aCTM,
00149                       void(*aCallback)(Graphics *, Brush*, void *), void *aData)
00150 {
00151   float fX1, fY1, fX2, fY2;
00152     
00153   nsCOMPtr<nsISVGLinearGradient>aLgrad = do_QueryInterface(aGrad);
00154   NS_ASSERTION(aLgrad, "error gradient did not provide a Linear Gradient interface");
00155   
00156   aLgrad->GetX1(&fX1);
00157   aLgrad->GetX2(&fX2);
00158   aLgrad->GetY1(&fY1);
00159   aLgrad->GetY2(&fY2);
00160 
00161   LinearGradientBrush gradient(PointF(fX1, fY1), PointF(fX2, fY2),
00162                                Color(0xff, 0, 0), Color(0, 0xff, 0));
00163 
00164   PRUint16 aSpread;
00165   aGrad->GetSpreadMethod(&aSpread);
00166   if (aSpread == nsIDOMSVGGradientElement::SVG_SPREADMETHOD_PAD)
00167     gradient.SetWrapMode(WrapModeTileFlipX);
00168   else if (aSpread == nsIDOMSVGGradientElement::SVG_SPREADMETHOD_REFLECT)
00169     gradient.SetWrapMode(WrapModeTileFlipX);
00170   else if (aSpread == nsIDOMSVGGradientElement::SVG_SPREADMETHOD_REPEAT)
00171     gradient.SetWrapMode(WrapModeTile);
00172 
00173   PRUint32 nStops;
00174   Color *stopsCol;
00175   REAL *stopsPos;
00176   GDIPlusGetStops(aGrad, &stopsCol, &stopsPos, &nStops, PR_FALSE);
00177   // If we have no stops, we don't want to render anything
00178   if (nStops == 0) {
00179     return;
00180   }
00181   gradient.SetInterpolationColors(stopsCol, stopsPos, nStops);
00182   SolidBrush leftBrush(stopsCol[0]);
00183   SolidBrush rightBrush(stopsCol[nStops-1]);
00184   delete [] stopsCol;
00185   delete [] stopsPos;
00186 
00187   gradient.MultiplyTransform(aMatrix, MatrixOrderAppend);
00188   gradient.MultiplyTransform(aCTM, MatrixOrderAppend);
00189 
00190   if (aSpread == nsIDOMSVGGradientElement::SVG_SPREADMETHOD_PAD) {
00191     float dx = fX2 - fX1;
00192     float dy = fY2 - fY1;
00193 
00194     // what we really want is a halfspace, but we don't have that...
00195 #define INF 100
00196 
00197     PointF rect[4];
00198     
00199     GraphicsPath left;
00200     left.StartFigure();
00201     rect[0].X = fX1 + INF * dy;             rect[0].Y = fY1 - INF * dx;
00202     rect[1].X = fX1 + INF * dx + INF * dy;  rect[1].Y = fY1 + INF * dy - INF * dx;
00203     rect[2].X = fX1 + INF * dx - INF * dy;  rect[2].Y = fY1 + INF * dy + INF * dx;
00204     rect[3].X = fX1 - INF * dy;             rect[3].Y = fY1 + INF * dx;
00205     left.AddPolygon(rect, 4);
00206     left.Transform(aMatrix);
00207     left.Transform(aCTM);
00208     
00209     GraphicsPath center;
00210     center.StartFigure();
00211     rect[0].X = fX1 + INF * dy;  rect[0].Y = fY1 - INF * dx;
00212     rect[1].X = fX2 + INF * dy;  rect[1].Y = fY2 - INF * dx;
00213     rect[2].X = fX2 - INF * dy;  rect[2].Y = fY2 + INF * dx;
00214     rect[3].X = fX1 - INF * dy;  rect[3].Y = fY1 + INF * dx;
00215     center.AddPolygon(rect, 4);
00216     center.Transform(aMatrix);
00217     center.Transform(aCTM);
00218     
00219     GraphicsPath right;
00220     right.StartFigure();
00221     rect[0].X = fX2 - INF * dx + INF * dy;  rect[0].Y = fY2 - INF * dy - INF * dx;
00222     rect[1].X = fX2 + INF * dy;             rect[1].Y = fY2 - INF * dx;
00223     rect[2].X = fX2 - INF * dy;             rect[2].Y = fY2 + INF * dx;
00224     rect[3].X = fX2 - INF * dx - INF * dy;  rect[3].Y = fY2 - INF * dy + INF * dx;
00225     right.AddPolygon(rect, 4);
00226     right.Transform(aMatrix);
00227     right.Transform(aCTM);
00228     
00229     Region leftRegion(&left), centerRegion(&center), rightRegion(&right), oldClip;
00230     aGFX->GetClip(&oldClip);
00231     
00232     aGFX->SetClip(&leftRegion, CombineModeExclude);
00233     aCallback(aGFX, &leftBrush, aData);
00234     aGFX->SetClip(&oldClip);
00235     
00236     aGFX->SetClip(&centerRegion, CombineModeIntersect);
00237     aCallback(aGFX, &gradient, aData);
00238     aGFX->SetClip(&oldClip);
00239     
00240     aGFX->SetClip(&rightRegion, CombineModeExclude);
00241     aCallback(aGFX, &rightBrush, aData);
00242     aGFX->SetClip(&oldClip);
00243   } else {
00244     aCallback(aGFX, &gradient, aData);
00245   }
00246 }
00247 
00248 static void
00249 GDIPlusRadialGradient(nsISVGGradient *aGrad, Matrix *aMatrix,
00250                       Graphics *aGFX, Matrix *aCTM, 
00251                       void(*aCallback)(Graphics *, Brush*, void *), void *aData)
00252 {
00253   float fCx, fCy, fR, fFx, fFy;
00254 
00255   // Get the Radial Gradient interface
00256   nsCOMPtr<nsISVGRadialGradient>aRgrad = do_QueryInterface(aGrad);
00257   NS_ASSERTION(aRgrad, "error gradient did not provide a Linear Gradient interface");
00258 
00259   aRgrad->GetCx(&fCx);
00260   aRgrad->GetCy(&fCy);
00261   aRgrad->GetR(&fR);
00262   aRgrad->GetFx(&fFx);
00263   aRgrad->GetFy(&fFy);
00264 
00265   GraphicsPath circle;
00266   circle.StartFigure();
00267   circle.AddEllipse(fCx - fR, fCy - fR, 2 * fR, 2 * fR);
00268 
00269   PathGradientBrush gradient(&circle);
00270   gradient.SetCenterPoint(PointF(fFx, fFy));
00271 
00272   PRUint16 aSpread;
00273   aGrad->GetSpreadMethod(&aSpread);
00274   if (aSpread == nsIDOMSVGGradientElement::SVG_SPREADMETHOD_PAD) {
00275       gradient.SetWrapMode(WrapModeClamp);
00276   }  else if (aSpread == nsIDOMSVGGradientElement::SVG_SPREADMETHOD_REFLECT)
00277       gradient.SetWrapMode(WrapModeTileFlipX);
00278   else if (aSpread == nsIDOMSVGGradientElement::SVG_SPREADMETHOD_REPEAT)
00279       gradient.SetWrapMode(WrapModeTile);
00280 
00281   PRUint32 nStops;
00282   Color *stopsCol;
00283   REAL *stopsPos;
00284   GDIPlusGetStops(aGrad, &stopsCol, &stopsPos, &nStops, PR_TRUE);
00285   // If we have no stops, we don't want to render anything
00286   if (nStops == 0) {
00287     return;
00288   }
00289   gradient.SetInterpolationColors(stopsCol, stopsPos, nStops);
00290   SolidBrush rimBrush(stopsCol[0]);
00291   delete [] stopsCol;
00292   delete [] stopsPos;
00293 
00294   gradient.MultiplyTransform(aMatrix, MatrixOrderAppend);
00295   gradient.MultiplyTransform(aCTM, MatrixOrderAppend);
00296   
00297   if (aSpread == nsIDOMSVGGradientElement::SVG_SPREADMETHOD_PAD) {
00298     circle.Transform(aMatrix);
00299     circle.Transform(aCTM);
00300     Region exclude(&circle), oldClip;
00301 
00302     aGFX->GetClip(&oldClip);
00303     aGFX->SetClip(&exclude, CombineModeExclude);
00304     aCallback(aGFX, &rimBrush, aData);
00305     aGFX->SetClip(&oldClip);
00306   }
00307 
00308   aCallback(aGFX, &gradient, aData);
00309 }
00310 
00311 
00312 void
00313 GDIPlusGradient(nsISVGGDIPlusRegion *aRegion, nsISVGGradient *aGrad,
00314                 nsIDOMSVGMatrix *aCTM,
00315                 Graphics *aGFX,
00316                 nsISVGGeometrySource *aSource,
00317                 void(*aCallback)(Graphics *, Brush*, void *), void *aData)
00318 {
00319   NS_ASSERTION(aGrad, "Called GDIPlusGradient without a gradient!");
00320   if (!aGrad)
00321     return;
00322 
00323   // Get the gradientUnits
00324   PRUint16 bbox;
00325   aGrad->GetGradientUnits(&bbox);
00326 
00327   // Get the transform list (if there is one)
00328   nsCOMPtr<nsIDOMSVGMatrix> svgMatrix;
00329   aGrad->GetGradientTransform(getter_AddRefs(svgMatrix), aSource);
00330   NS_ASSERTION(svgMatrix, "GDIPlusGradient: GetGradientTransform returns null");
00331 
00332   // GDI+ gradients don't like having a zero on the diagonal (zero
00333   // width or height in the bounding box)
00334   float val;
00335   svgMatrix->GetA(&val);
00336   if (val == 0.0)
00337     svgMatrix->SetA(1.0);
00338   svgMatrix->GetD(&val);
00339   if (val == 0.0)
00340     svgMatrix->SetD(1.0);
00341 
00342   Matrix *patternMatrix =  SVGToMatrix(svgMatrix);
00343   Matrix *ctm = SVGToMatrix(aCTM);
00344 
00345   // Linear or Radial?
00346   PRUint32 type;
00347   aGrad->GetGradientType(&type);
00348   if (type == nsISVGGradient::SVG_LINEAR_GRADIENT)
00349     GDIPlusLinearGradient(aGrad, patternMatrix, aGFX, ctm, aCallback, aData);
00350   else if (type == nsISVGGradient::SVG_RADIAL_GRADIENT)
00351     GDIPlusRadialGradient(aGrad, patternMatrix, aGFX, ctm, aCallback, aData);
00352 
00353   delete patternMatrix;
00354   delete ctm;
00355 }