00001 // $Id: ai_grad.cpp 1313 2006-06-13 16:55:22Z alex $ 00002 // ai_grad.cpp: implementation of the AIGradientProcessor class. 00003 // 00005 /* @@tag:xara-cn@@ DO NOT MODIFY THIS LINE 00006 ================================XARAHEADERSTART=========================== 00007 00008 Xara LX, a vector drawing and manipulation program. 00009 Copyright (C) 1993-2006 Xara Group Ltd. 00010 Copyright on certain contributions may be held in joint with their 00011 respective authors. See AUTHORS file for details. 00012 00013 LICENSE TO USE AND MODIFY SOFTWARE 00014 ---------------------------------- 00015 00016 This file is part of Xara LX. 00017 00018 Xara LX is free software; you can redistribute it and/or modify it 00019 under the terms of the GNU General Public License version 2 as published 00020 by the Free Software Foundation. 00021 00022 Xara LX and its component source files are distributed in the hope 00023 that it will be useful, but WITHOUT ANY WARRANTY; without even the 00024 implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 00025 See the GNU General Public License for more details. 00026 00027 You should have received a copy of the GNU General Public License along 00028 with Xara LX (see the file GPL in the root directory of the 00029 distribution); if not, write to the Free Software Foundation, Inc., 51 00030 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 00031 00032 00033 ADDITIONAL RIGHTS 00034 ----------------- 00035 00036 Conditional upon your continuing compliance with the GNU General Public 00037 License described above, Xara Group Ltd grants to you certain additional 00038 rights. 00039 00040 The additional rights are to use, modify, and distribute the software 00041 together with the wxWidgets library, the wxXtra library, and the "CDraw" 00042 library and any other such library that any version of Xara LX relased 00043 by Xara Group Ltd requires in order to compile and execute, including 00044 the static linking of that library to XaraLX. In the case of the 00045 "CDraw" library, you may satisfy obligation under the GNU General Public 00046 License to provide source code by providing a binary copy of the library 00047 concerned and a copy of the license accompanying it. 00048 00049 Nothing in this section restricts any of the rights you have under 00050 the GNU General Public License. 00051 00052 00053 SCOPE OF LICENSE 00054 ---------------- 00055 00056 This license applies to this program (XaraLX) and its constituent source 00057 files only, and does not necessarily apply to other Xara products which may 00058 in part share the same code base, and are subject to their own licensing 00059 terms. 00060 00061 This license does not apply to files in the wxXtra directory, which 00062 are built into a separate library, and are subject to the wxWindows 00063 license contained within that directory in the file "WXXTRA-LICENSE". 00064 00065 This license does not apply to the binary libraries (if any) within 00066 the "libs" directory, which are subject to a separate license contained 00067 within that directory in the file "LIBS-LICENSE". 00068 00069 00070 ARRANGEMENTS FOR CONTRIBUTION OF MODIFICATIONS 00071 ---------------------------------------------- 00072 00073 Subject to the terms of the GNU Public License (see above), you are 00074 free to do whatever you like with your modifications. However, you may 00075 (at your option) wish contribute them to Xara's source tree. You can 00076 find details of how to do this at: 00077 http://www.xaraxtreme.org/developers/ 00078 00079 Prior to contributing your modifications, you will need to complete our 00080 contributor agreement. This can be found at: 00081 http://www.xaraxtreme.org/developers/contribute/ 00082 00083 Please note that Xara will not accept modifications which modify any of 00084 the text between the start and end of this header (marked 00085 XARAHEADERSTART and XARAHEADEREND). 00086 00087 00088 MARKS 00089 ----- 00090 00091 Xara, Xara LX, Xara X, Xara X/Xtreme, Xara Xtreme, the Xtreme and Xara 00092 designs are registered or unregistered trademarks, design-marks, and/or 00093 service marks of Xara Group Ltd. All rights in these marks are reserved. 00094 00095 00096 Xara Group Ltd, Gaddesden Place, Hemel Hempstead, HP2 6EX, UK. 00097 http://www.xara.com/ 00098 00099 =================================XARAHEADEREND============================ 00100 */ 00101 00102 #include "camtypes.h" 00103 #include "ai_grad.h" 00104 00105 #include "ai5_eps.h" 00106 #include "fillramp.h" 00107 00108 DECLARE_SOURCE("$Revision"); 00109 00110 #define new CAM_DEBUG_NEW 00111 00112 /******************************************************************************************** 00113 00114 > AI5Gradient::AI5Gradient() 00115 00116 Author: Tim_Browse (Xara Group Ltd) <camelotdev@xara.com> 00117 Created: 12/08/94 00118 Purpose: Set up a gradient record to sensible values (linear fill black to black). 00119 SeeAlso: AI5Gradient; AI5EPSFilter 00120 00121 ********************************************************************************************/ 00122 AI5Gradient::AI5Gradient() : 00123 IsRadial( FALSE ), 00124 StartColour( BLACK ), 00125 EndColour( BLACK ), 00126 mpCurrentRamp( 0 ) 00127 { 00128 } 00129 00130 00131 AI5Gradient::~AI5Gradient() 00132 { 00133 delete mpCurrentRamp; 00134 mpCurrentRamp = 0; 00135 } 00136 00139 00140 00141 00142 00143 00145 // Construction/Destruction 00147 00148 AIGradientProcessor::AIGradientProcessor() : 00149 mbGradFillTooComplex(FALSE), 00150 mpCurrentFill(0) 00151 { 00152 // The last ramp point is set to 101, since this is effectively a 00153 // counter from 100 down to 0. 00154 mMidPoint = 0; 00155 mLastRampPoint = 101; 00156 } 00157 00158 AIGradientProcessor::~AIGradientProcessor() 00159 { 00160 mGradFills.DeleteAll(); 00161 00162 delete mpCurrentFill; 00163 mpCurrentFill = 0; 00164 } 00165 00166 00167 /******************************************************************************************** 00168 00169 > BOOL AIGradientProcessor::BeginGradient() 00170 00171 Author: Colin_Barfoot (Xara Group Ltd) <camelotdev@xara.com> 00172 Created: 23/03/00 00173 00174 Purpose: Allows the gradient processor to intialize itself for a new gradient 00175 signalled by the %BeginGradient comment. 00176 00177 Returns: TRUE if the definition was processed, 00178 FALSE if not used by this filter.. 00179 00180 ********************************************************************************************/ 00181 BOOL AIGradientProcessor::BeginGradient() 00182 { 00183 ENSURE( mpCurrentFill == NULL, "mpCurrentFill is not NULL"); 00184 00185 // Found the start of a gradient definition - prepare to read in the 00186 // definition... 00187 mpCurrentFill = new AI5Gradient; 00188 00189 if ( !mpCurrentFill ) 00190 return FALSE; 00191 00192 return TRUE; 00193 } 00194 00195 00196 00197 /******************************************************************************************** 00198 00199 > BOOL AIGradientProcessor::DecodeBd( AI5EPSFilter& filter ) 00200 00201 Author: Colin_Barfoot (Xara Group Ltd) <camelotdev@xara.com> 00202 Created: 23/03/00 00203 00204 Purpose: Processes the gradient definition start operator which tells us how many 00205 colours are in it and what its name is. 00206 00207 Returns: TRUE if the definition was processed, 00208 FALSE if not used by this filter.. 00209 00210 ********************************************************************************************/ 00211 BOOL AIGradientProcessor::DecodeBd( AI5EPSFilter& filter ) 00212 { 00213 if ( !mpCurrentFill ) 00214 return FALSE; 00215 00217 // Name, type, and number of steps in gradient fill 00219 INT32 colours = 0; 00220 INT32 type = 0; 00221 00222 if (!filter.GetStack().Pop(&colours) || 00223 !filter.GetStack().Pop(&type) || 00224 !filter.GetStack().Pop(&mpCurrentFill->Name)) 00225 // Error in syntax 00226 return FALSE; 00227 00228 // Translate fill type 00229 switch (type) 00230 { 00231 case 0: mpCurrentFill->IsRadial = FALSE; break; 00232 case 1: mpCurrentFill->IsRadial = TRUE; break; 00233 default: return FALSE; 00234 } 00235 00236 return TRUE; 00237 } 00238 00239 /******************************************************************************************** 00240 00241 > BOOL AIGradientProcessor::DecodeBS( AI5EPSFilter& filter ) 00242 00243 Author: Colin_Barfoot (Xara Group Ltd) <camelotdev@xara.com> 00244 Created: 23/03/00 00245 00246 Purpose: Processes the gradient stop operator (indicating a step in the ramp) 00247 00248 Returns: TRUE if the definition was processed, 00249 FALSE if not used by this filter.. 00250 00251 ********************************************************************************************/ 00252 BOOL AIGradientProcessor::DecodeBS( AI5EPSFilter& filter ) 00253 { 00254 // Decode colour/step definition 00255 INT32 rampPoint = 0, midPoint = 0; 00256 00257 if ( !filter.GetStack().Pop(&rampPoint) || !filter.GetStack().Pop(&midPoint) ) 00258 return FALSE; 00259 00260 // Check if these are values we can handle 00261 // (We still import an approximation of the fill even if not) 00262 if ( midPoint > 100 || midPoint < 0 || rampPoint < 0 || rampPoint > 100 ) 00263 { 00264 return FALSE; 00265 } 00266 00267 // Work out what sort of colour this is - CMYK or B&W tint 00268 INT32 colorStyle; 00269 if ( !filter.GetStack().Pop(&colorStyle) ) 00270 return FALSE; 00271 00272 DocColour newColour; 00273 00274 switch ( colorStyle ) 00275 { 00276 case 0: // grey 00277 { 00278 double dTint( 0.0 ); 00279 if ( !filter.GetStack().Pop(&dTint) ) 00280 return FALSE; 00281 00282 // Make a colour from this 00283 INT32 intensity = (INT32) (dTint * 255.0); 00284 newColour.SetRGBValue( intensity, intensity, intensity ); 00285 break; 00286 } 00287 00288 case 1: // CMYK 00289 { 00290 PColourCMYK col; 00291 if (!filter.GetStack().PopColour(&col, TINT_NONE)) 00292 return FALSE; 00293 00294 // Set up the colour with what we have. 00295 newColour.SetCMYKValue( &col ); 00296 00297 break; 00298 } 00299 00300 case 2: // RGB 00301 { 00302 INT32 red, green, blue; 00303 if (!filter.GetStack().PopColourRGB (&red, &green, &blue, TINT_NONE)) 00304 return FALSE; 00305 00306 // remove the CMYK colour info. 00307 (filter.GetStack()).Discard (4); 00308 newColour.SetRGBValue (red, green, blue); 00309 break; 00310 } 00311 00312 case 3: // CMYK custom 00313 { 00314 PColourCMYK col; 00315 FIXEDPOINT tint; 00316 String_64 name; 00317 if ( !filter.GetStack().PopColour( &col, TINT_ILLUSTRATOR, &tint, &name ) ) 00318 return FALSE; 00319 00320 newColour.SetCMYKValue( &col ); 00321 00322 break; 00323 } 00324 00325 case 4: // RGB custom 00326 { 00327 INT32 red, green, blue; 00328 FIXEDPOINT tint; 00329 String_64 name; 00330 00331 // remove the 'type' flag (is always 1 for RGB) 00332 filter.GetStack().Discard (1); 00333 00334 if (!filter.GetStack().PopColourRGB (&red, &green, &blue, 00335 TINT_ILLUSTRATOR, &tint, &name)) 00336 return FALSE; 00337 00338 (filter.GetStack()).Discard (4); 00339 newColour.SetRGBValue (red, green, blue); 00340 00341 break; 00342 } 00343 default: 00344 break; 00345 } 00346 00347 00349 // add the colour to the ramp 00351 00352 double dPos = double(rampPoint) / double(100.0); 00353 00354 if ( mpCurrentFill ) 00355 { 00356 if ( !mpCurrentFill->mpCurrentRamp ) 00357 { 00358 mpCurrentFill->mpCurrentRamp = new ColourRamp; 00359 if ( ! mpCurrentFill->mpCurrentRamp ) 00360 { 00361 return FALSE; 00362 } 00363 } 00364 00365 mpCurrentFill->mpCurrentRamp->AddEntry( static_cast<float> ( dPos ), &newColour ); 00366 } 00367 00368 // Set the midpoint if this point is nearer to the start than the stored mid-point. 00369 // The midpoint nearest to the 0% ramp point is used, as there should only be one 00370 // midpoint if Xara X's profiling is going to cope with it, and the last midpoint in 00371 // an Illustrator gradient should always be ignored. 00372 if (rampPoint < mLastRampPoint) 00373 { 00374 mMidPoint = midPoint; 00375 mLastRampPoint = rampPoint; 00376 } 00377 00378 return TRUE; 00379 } 00380 00381 00382 /******************************************************************************************** 00383 00384 > BOOL AIGradientProcessor::DecodeBh( AI5EPSFilter& filter ) 00385 00386 Author: Colin_Barfoot (Xara Group Ltd) <camelotdev@xara.com> 00387 Created: 23/03/00 00388 00389 Purpose: The Bh operator adds a highlight to a fill. In Camelot we interpret this 00390 as a conical fill. 00391 00392 Returns: TRUE if the definition was processed happily 00393 FALSE if not 00394 00395 00396 The format of the operator is: 00397 xHilight yHilight angle length Bh 00398 00399 Arguments to the Bh operator are as follows: 00400 xHilight yHilight These arguments specify the hilight placement, in x and y offsets 00401 from the gradient vector origin. 00402 angle This argument is the angle to the hilight point, measured 00403 counterclockwise from the x axis. 00404 length This argument is the distance of the hilight from the origin, 00405 expressed as a fraction of the radius a value between 0 and 1. 00406 00407 00408 ********************************************************************************************/ 00409 BOOL AIGradientProcessor::DecodeBh( AI5EPSFilter& filter ) 00410 { 00412 // read the arguments from the stack 00414 00415 INT32 nAngle = 0; 00416 double dLength = 0.0; 00417 DocCoord hilight; 00418 00419 if ( !filter.GetStack().Pop(&dLength) || !filter.GetStack().Pop(&nAngle) || 00420 !filter.GetStack().PopCoordPair(&hilight) ) 00421 return FALSE; 00422 00424 // Check if these are values we can handle 00426 00428 // Attach the highlight details so we can work out what to do later 00429 // but if there's already a highlight ignore this one 00431 /* 00432 00433 Won't do this for now 00434 00435 00436 mpCurrentHighlight = new AI5Gradient::Highlight( nAngle, dLength, hilight ); 00437 */ 00438 return TRUE; 00439 } 00440 00441 /******************************************************************************************** 00442 00443 > BOOL AIGradientProcessor::Approx (const double & d1, const double & d2) 00444 00445 Author: ? 00446 Created: presumably March 2000 00447 Inputs: d1, d2 - two doubles that are to be compared 00448 Purpose: This is used to determine whether the position of one gradient stop is 00449 equivalent to another during import, mainly for working out which stops 00450 are at the start and the end. 00451 00452 Currently the tolerance for this is 2% either way, as this seems to give 00453 good results. 00454 00455 Returns: TRUE if the d1 is approximately equal to d2 00456 FALSE if not 00457 00458 ********************************************************************************************/ 00459 BOOL AIGradientProcessor::Approx (const double & d1, const double & d2) 00460 { 00461 const double dTolerance = 2; 00462 return ( fabs( d2 - d1 ) < dTolerance ); 00463 } 00464 00465 00466 /******************************************************************************************** 00467 00468 > BOOL AIGradientProcessor::EndGradient() 00469 00470 Author: Colin_Barfoot (Xara Group Ltd) <camelotdev@xara.com> 00471 Created: 23/03/00 00472 00473 Purpose: Allows the gradient processor to complete the gradient 00474 signalled by the %EndGradient comment. 00475 00476 Returns: TRUE if the definition was processed, 00477 FALSE if not used by this filter.. 00478 00479 ********************************************************************************************/ 00480 BOOL AIGradientProcessor::EndGradient( AI5EPSFilter& filter ) 00481 { 00482 ENSURE( mpCurrentFill != NULL, "mpGradFill is NULL"); 00483 00484 // The end of the definition - lose the rampant '[' from the input stream. 00485 EPSCommand Cmd; 00486 if (!filter.GetStack().PopCmd(&Cmd) || (Cmd != EPSC_ArrayStart)) 00487 { 00488 // if it isn't what we're hoping for, ignore it 00489 filter.GetStack().Push( Cmd ); 00490 } 00491 00493 // an n-stage multi-stage fill in camelot is divided into n - 2 ramp items 00494 // and the start and end colours of the fill. 00495 // When creating the fill we have just added ramp items, so we need to pick 00496 // off the first and last for the start and end colours. 00498 00499 if ( mpCurrentFill->mpCurrentRamp ) 00500 { 00501 // (ChrisG 3/4/2001) Gradient stops are now always removed from the list if they are 00502 // used as a start or end colour. There was a check to make sure they were near the 00503 // end, which often failed, leading to additional stops at the ends. 00504 00505 // first the start colour 00506 ColRampItem* pFirstItem = reinterpret_cast<ColRampItem*>( mpCurrentFill->mpCurrentRamp->GetHead() ); 00507 if ( pFirstItem ) 00508 { 00509 // Take the first colour out of the ramp, as this is represented by the start colour 00510 mpCurrentFill->StartColour = pFirstItem->GetColour(); 00511 00512 // if the first ramp item isn't at the start of the fill, leave it as 00513 // part of the ramp and make the start the same colour 00514 if ( Approx( pFirstItem->GetPosition(), 0.0) || 00515 Approx( pFirstItem->GetPosition(), 100.0)) 00516 { 00517 mpCurrentFill->mpCurrentRamp->RemoveHead(); 00518 } 00519 } 00520 00521 // then the end colour 00522 ColRampItem* pLastItem = reinterpret_cast<ColRampItem*>( mpCurrentFill->mpCurrentRamp->GetTail() ); 00523 if ( pLastItem ) 00524 { 00525 // Take the last colour out of the ramp, as this is represented by the end colour 00526 mpCurrentFill->EndColour = pLastItem->GetColour(); 00527 00528 // if the first ramp item isn't at the start of the fill, leave it as 00529 // part of the ramp and make the start the same colour 00530 if ( Approx( pLastItem->GetPosition(), 0.0) || 00531 Approx( pLastItem->GetPosition(), 100.0)) 00532 { 00533 mpCurrentFill->mpCurrentRamp->RemoveTail(); 00534 } 00535 } 00536 } 00537 00538 // set the mid-point, for the profiling. 00539 mpCurrentFill->midPoint = mMidPoint; 00540 00541 // add the current fill to our list. 00542 mGradFills.AddTail( mpCurrentFill ); 00543 00544 // We're done with this fill. 00545 mpCurrentFill = NULL; 00546 00547 // reset the profiling variables. 00548 mMidPoint = 0; 00549 mLastRampPoint = 101; 00550 00551 return TRUE; 00552 } 00553 00554 00555 /******************************************************************************************** 00556 00557 > AI5Gradient* AIGradientProcessor::FindGradient( const StringBase& name ) 00558 00559 Author: Colin_Barfoot (Xara Group Ltd) <camelotdev@xara.com> 00560 Created: 23/03/00 00561 00562 Purpose: Once the gradient definitions have been read, gradient instances can be 00563 created using the name to identify the gradient "style". 00564 00565 Returns: NULL if no gradient exists with the given name 00566 Otherwise the one that does. 00567 00568 ********************************************************************************************/ 00569 AI5Gradient* AIGradientProcessor::FindGradient( const StringBase& name ) 00570 { 00571 // First find this named gradient fill 00572 AI5Gradient* pGradient = (AI5Gradient *) mGradFills.GetHead(); 00573 00574 while ( pGradient ) 00575 { 00576 if ( pGradient->Name == name ) 00577 // This is the one we want! 00578 break; 00579 00580 // Try the next one 00581 pGradient = (AI5Gradient *) mGradFills.GetNext(pGradient); 00582 } 00583 00584 return pGradient; 00585 }