xref: /AOO41X/main/filter/source/flash/swfwriter2.cxx (revision 9e0fc027f109ec4ffcb6033aeec742a099701108)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_filter.hxx"
26 #include "swfwriter.hxx"
27 #include <vcl/virdev.hxx>
28 #include <basegfx/matrix/b2dhommatrixtools.hxx>
29 
30 #include <math.h>
31 
32 using namespace ::swf;
33 using namespace ::std;
34 using namespace ::com::sun::star::uno;
35 using namespace ::com::sun::star::io;
36 using ::rtl::OUString;
37 using ::rtl::OString;
38 
39 // -----------------------------------------------------------------------------
40 
getMaxBitsUnsigned(sal_uInt32 nValue)41 sal_uInt16 getMaxBitsUnsigned( sal_uInt32 nValue )
42 {
43     sal_uInt16 nBits = 0;
44 
45     while( nValue )
46     {
47         nBits++;
48         nValue >>= 1;
49     }
50 
51     return nBits;
52 }
53 
54 // -----------------------------------------------------------------------------
55 
getMaxBitsSigned(sal_Int32 nValue)56 sal_uInt16 getMaxBitsSigned( sal_Int32 nValue )
57 {
58     if( nValue < 0 )
59         nValue *= -1;
60 
61     return getMaxBitsUnsigned( static_cast< sal_uInt32 >(nValue) ) + 1;
62 }
63 
64 // -----------------------------------------------------------------------------
65 
BitStream()66 BitStream::BitStream()
67 {
68     mnBitPos = 8;
69     mnCurrentByte = 0;
70 }
71 
72 // -----------------------------------------------------------------------------
73 
writeUB(sal_uInt32 nValue,sal_uInt16 nBits)74 void BitStream::writeUB( sal_uInt32 nValue, sal_uInt16 nBits )
75 {
76     while( nBits != 0 )
77     {
78         mnCurrentByte |= nValue << (32 - nBits) >> (32 - mnBitPos);
79 
80         if ( nBits > mnBitPos )
81         {
82             nBits = nBits - mnBitPos;
83             mnBitPos = 0;
84         }
85         else
86         {
87             mnBitPos = sal::static_int_cast<sal_uInt8>( mnBitPos - nBits );
88             nBits = 0;
89         }
90 
91         if( 0 == mnBitPos )
92             pad();
93     }
94 }
95 
96 // -----------------------------------------------------------------------------
97 
writeSB(sal_Int32 nValue,sal_uInt16 nBits)98 void BitStream::writeSB( sal_Int32 nValue, sal_uInt16 nBits )
99 {
100     writeUB( static_cast< sal_uInt32 >(nValue), nBits );
101 }
102 
103 // -----------------------------------------------------------------------------
104 
writeFB(sal_uInt32 nValue,sal_uInt16 nBits)105 void BitStream::writeFB( sal_uInt32 nValue, sal_uInt16 nBits )
106 {
107     writeUB( nValue, nBits );
108 }
109 
110 // -----------------------------------------------------------------------------
111 
pad()112 void BitStream::pad()
113 {
114     if( 8 != mnBitPos )
115     {
116         maData.push_back( mnCurrentByte );
117         mnCurrentByte = 0;
118         mnBitPos = 8;
119     }
120 }
121 
122 // -----------------------------------------------------------------------------
123 
writeTo(SvStream & out)124 void BitStream::writeTo( SvStream& out )
125 {
126     pad();
127 
128     vector< sal_uInt8 >::iterator aIter( maData.begin() );
129     const vector< sal_uInt8>::iterator aEnd( maData.end() );
130     while(aIter != aEnd)
131     {
132         out << (*aIter++);
133     }
134 }
135 
136 // -----------------------------------------------------------------------------
137 
getOffset() const138 sal_uInt32 BitStream::getOffset() const
139 {
140     return maData.size();
141 }
142 
143 ////////////////////////////////////////////////////////////////////////////////
144 
Tag(sal_uInt8 nTagId)145 Tag::Tag( sal_uInt8 nTagId )
146 {
147     mnTagId = nTagId;
148 }
149 
150 // -----------------------------------------------------------------------------
151 
write(SvStream & out)152 void Tag::write( SvStream &out )
153 {
154     Seek( STREAM_SEEK_TO_END );
155     sal_uInt32 nSz = Tell();
156     Seek( STREAM_SEEK_TO_BEGIN );
157 
158     if( mnTagId != 0xff )
159     {
160         bool bLarge = nSz > 62;
161 
162         sal_uInt16 nCode = ( mnTagId << 6 ) | ( bLarge ? 0x3f : _uInt16(nSz) );
163 
164         out << (sal_uInt8)nCode;
165         out << (sal_uInt8)(nCode >> 8);
166 
167         if( bLarge )
168         {
169             sal_uInt32 nTmp = nSz;
170 
171             out << (sal_uInt8)nTmp;
172             nTmp >>= 8;
173             out << (sal_uInt8)nTmp;
174             nTmp >>= 8;
175             out << (sal_uInt8)nTmp;
176             nTmp >>= 8;
177             out << (sal_uInt8)nTmp;
178         }
179     }
180 
181     out.Write( GetData(), nSz );
182 }
183 #if 0
184 // -----------------------------------------------------------------------------
185 
186 void Tag::addI32( sal_Int32 nValue )
187 {
188     addUI32( static_cast<sal_uInt32>( nValue ) );
189 }
190 #endif
191 // -----------------------------------------------------------------------------
192 
addUI32(sal_uInt32 nValue)193 void Tag::addUI32( sal_uInt32 nValue )
194 {
195     *this << nValue;
196 }
197 #if 0
198 // -----------------------------------------------------------------------------
199 
200 void Tag::addI16( sal_Int16 nValue )
201 {
202     addUI16( static_cast<sal_uInt16>( nValue ) );
203 }
204 #endif
205 // -----------------------------------------------------------------------------
206 
addUI16(sal_uInt16 nValue)207 void Tag::addUI16( sal_uInt16 nValue )
208 {
209     *this << (sal_uInt8)nValue;
210     *this << (sal_uInt8)(nValue >> 8);
211 }
212 
213 // -----------------------------------------------------------------------------
214 
addUI8(sal_uInt8 nValue)215 void Tag::addUI8( sal_uInt8 nValue )
216 {
217     *this << (sal_uInt8)nValue;
218 }
219 
220 // -----------------------------------------------------------------------------
221 
addBits(BitStream & rIn)222 void Tag::addBits( BitStream& rIn )
223 {
224     rIn.writeTo( *this );
225 }
226 
227 // -----------------------------------------------------------------------------
228 
addRGBA(const Color & rColor)229 void Tag::addRGBA( const Color& rColor )
230 {
231     addUI8( rColor.GetRed() );
232     addUI8( rColor.GetGreen() );
233     addUI8( rColor.GetBlue() );
234     addUI8( 0xff - rColor.GetTransparency() );
235 }
236 
237 // -----------------------------------------------------------------------------
238 
addRGB(const Color & rColor)239 void Tag::addRGB( const Color& rColor )
240 {
241     addUI8( rColor.GetRed() );
242     addUI8( rColor.GetGreen() );
243     addUI8( rColor.GetBlue() );
244 }
245 
246 // -----------------------------------------------------------------------------
247 
addRect(const Rectangle & rRect)248 void Tag::addRect( const Rectangle& rRect )
249 {
250     writeRect( *this, rRect );
251 }
252 
253 // -----------------------------------------------------------------------------
254 
writeRect(SvStream & rOut,const Rectangle & rRect)255 void Tag::writeRect( SvStream& rOut, const Rectangle& rRect )
256 {
257     BitStream aBits;
258 
259     sal_Int32 minX, minY, maxX, maxY;
260 
261     if( rRect.nLeft < rRect.nRight )
262     {
263         minX = rRect.nLeft; maxX = rRect.nRight;
264     }
265     else
266     {
267         maxX = rRect.nLeft; minX = rRect.nRight;
268     }
269 
270 
271     if( rRect.nTop < rRect.nBottom )
272     {
273         minY = rRect.nTop; maxY = rRect.nBottom;
274     }
275     else
276     {
277         maxY = rRect.nTop; minY = rRect.nBottom;
278     }
279 
280     // AS: Figure out the maximum nubmer of bits required to represent any of the
281     //  rectangle coordinates.  Since minX or minY could be negative, they could
282     //  actually require more bits than maxX or maxY.
283     // AS: Christian, can they be negative, or is that a wasted check?
284     // CL: I think so, f.e. for shapes that have the top and/or left edge outside
285     //         the page origin
286     sal_uInt8 nBits1 = sal::static_int_cast<sal_uInt8>( max( getMaxBitsSigned( minX ), getMaxBitsSigned( minY ) ) );
287     sal_uInt8 nBits2 = sal::static_int_cast<sal_uInt8>( max( getMaxBitsSigned( maxX ), getMaxBitsSigned( maxY ) ) );
288     sal_uInt8 nBitsMax = max( nBits1, nBits2 );
289 
290     aBits.writeUB( nBitsMax, 5 );
291     aBits.writeSB( minX, nBitsMax );
292     aBits.writeSB( maxX, nBitsMax );
293     aBits.writeSB( minY, nBitsMax );
294     aBits.writeSB( maxY, nBitsMax );
295 
296     aBits.writeTo( rOut );
297 }
298 
299 // -----------------------------------------------------------------------------
300 
addMatrix(const::basegfx::B2DHomMatrix & rMatrix)301 void Tag::addMatrix( const ::basegfx::B2DHomMatrix& rMatrix ) // #i73264#
302 {
303     writeMatrix( *this, rMatrix );
304 }
305 
306 // -----------------------------------------------------------------------------
307 
writeMatrix(SvStream & rOut,const::basegfx::B2DHomMatrix & rMatrix)308 void Tag::writeMatrix( SvStream& rOut, const ::basegfx::B2DHomMatrix& rMatrix ) // #i73264#
309 {
310 
311     BitStream aBits;
312 
313     const sal_uInt8 bHasScale = rMatrix.get(0, 0) != 1.0 || rMatrix.get(1, 1) != 1.0;
314 
315     aBits.writeUB( bHasScale, 1 );
316 
317     if( bHasScale )
318     {
319         sal_uInt8 nScaleBits = 31;
320 
321         aBits.writeUB( nScaleBits, 5 );
322         aBits.writeFB( getFixed( rMatrix.get(0, 0) ), nScaleBits ); // Scale X
323         aBits.writeFB( getFixed( rMatrix.get(1, 1) ), nScaleBits ); // Scale Y
324     }
325 
326     const sal_uInt8 bHasRotate = rMatrix.get(0, 1) != 0.0 || rMatrix.get(1, 0) != 0.0;
327 
328     aBits.writeUB( bHasRotate, 1 );
329 
330     if( bHasRotate )
331     {
332         sal_uInt8 nRotateBits = 31;
333 
334         aBits.writeUB( nRotateBits, 5 );
335         aBits.writeFB( getFixed( rMatrix.get(0, 1) ), nRotateBits );    // RotateSkew0
336         aBits.writeFB( getFixed( rMatrix.get(1, 0) ), nRotateBits );    // RotateSkew1
337     }
338 
339     sal_uInt8 nTranslateBits = 16;
340 
341     aBits.writeUB( nTranslateBits, 5 );
342     aBits.writeSB( (sal_Int16)rMatrix.get(0, 2), nTranslateBits );      // Translate X
343     aBits.writeSB( (sal_Int16)rMatrix.get(1, 2), nTranslateBits );      // Translate Y
344 
345     aBits.writeTo( rOut );
346 }
347 
348 // -----------------------------------------------------------------------------
349 
addString(const char * pString)350 void Tag::addString( const char* pString )
351 {
352     if( pString )
353     {
354         while( *pString )
355             addUI8( *pString++ );
356     }
357 
358     addUI8( 0 );
359 }
360 
361 // -----------------------------------------------------------------------------
362 
addStream(SvStream & rIn)363 void Tag::addStream( SvStream& rIn )
364 {
365     *this << rIn;
366 }
367 
368 ////////////////////////////////////////////////////////////////////////////////
369 
Sprite(sal_uInt16 nId)370 Sprite::Sprite( sal_uInt16 nId )
371 : mnId( nId ), mnFrames(0)
372 {
373 }
374 
375 // -----------------------------------------------------------------------------
376 
~Sprite()377 Sprite::~Sprite()
378 {
379     for(vector< Tag* >::iterator i = maTags.begin(); i != maTags.end(); i++)
380         delete *i;
381 }
382 
383 // -----------------------------------------------------------------------------
384 
write(SvStream & out)385 void Sprite::write( SvStream& out )
386 {
387     SvMemoryStream aTmp;
388     for(vector< Tag* >::iterator i = maTags.begin(); i != maTags.end(); i++)
389         (*i)->write( aTmp );
390 
391     if( !mnFrames )
392         mnFrames = 1;
393 
394     aTmp.Seek(0);
395 
396     Tag aTag( TAG_DEFINESPRITE );
397     aTag.addUI16( mnId );
398     aTag.addUI16( _uInt16( mnFrames ) );
399     aTag.addStream( aTmp );
400     aTag.write( out );
401 }
402 
403 // -----------------------------------------------------------------------------
404 
addTag(Tag * pNewTag)405 void Sprite::addTag( Tag* pNewTag )
406 {
407     if( pNewTag )
408     {
409         if( pNewTag->getTagId() == TAG_SHOWFRAME )
410             mnFrames++;
411 
412         maTags.push_back( pNewTag );
413     }
414 }
415 
416 /////////////////////////////////////////////////////////////////////////////////
417 
getFixed(double fValue)418 sal_uInt32 swf::getFixed( double fValue )
419 {
420     sal_Int16 nUpper = (sal_Int16)floor(fValue);
421     sal_uInt16 nLower = (sal_uInt16)((fValue - floor(fValue))*0x10000);
422 
423     sal_uInt32 temp = ((sal_Int32)nUpper)<<16;
424     temp |= nLower;
425 
426     return temp;
427 }
428 
429 /////////////////////////////////////////////////////////////////////////////////
430 
431 /** constructs a new flash font for the given VCL Font */
FlashFont(const Font & rFont,sal_uInt16 nId)432 FlashFont::FlashFont( const Font& rFont, sal_uInt16 nId )
433 : maFont( rFont ), mnNextIndex(0), mnId( nId )
434 {
435 }
436 
437 // -----------------------------------------------------------------------------
438 
~FlashFont()439 FlashFont::~FlashFont()
440 {
441 }
442 
443 // -----------------------------------------------------------------------------
444 
445 /** gets the glyph id for the given character. The glyphs are created on demand */
getGlyph(sal_uInt16 nChar,VirtualDevice * pVDev)446 sal_uInt16 FlashFont::getGlyph( sal_uInt16 nChar, VirtualDevice* pVDev )
447 {
448     // see if we already created a glyph for this character
449     std::map<sal_uInt16, sal_uInt16, ltuint16>::iterator aIter( maGlyphIndex.find(nChar) );
450     if( aIter != maGlyphIndex.end() )
451     {
452         return aIter->second;
453     }
454 
455     // if not, we create one now
456 
457     maGlyphIndex[nChar] = mnNextIndex;
458 
459     Font aOldFont( pVDev->GetFont() );
460     Font aNewFont( aOldFont );
461     aNewFont.SetAlign( ALIGN_BASELINE );
462     pVDev->SetFont( aNewFont );
463     aOldFont.SetOrientation(0);
464 
465     // let the virtual device convert the character to polygons
466     PolyPolygon aPolyPoly;
467     pVDev->GetTextOutline( aPolyPoly, nChar );
468 
469     maGlyphOffsets.push_back( _uInt16( maGlyphData.getOffset() ) );
470 
471     // Number of fill and line index bits set to 1
472     maGlyphData.writeUB( 0x11, 8 );
473 
474     const sal_uInt16 nCount = aPolyPoly.Count();
475     sal_uInt16 i,n;
476     for( i = 0; i < nCount; i++ )
477     {
478         Polygon& rPoly = aPolyPoly[ i ];
479 
480         const sal_uInt16 nSize = rPoly.GetSize();
481         if( nSize )
482         {
483             // convert polygon to flash EM_SQUARE (1024x1024)
484             for( n = 0; n < nSize; n++ )
485             {
486                 Point aPoint( rPoly[n] );
487                 aPoint.X() = static_cast<long>((double(aPoint.X()) * 1024.0 ) / double(aOldFont.GetHeight()));
488                 aPoint.Y() = static_cast<long>((double(aPoint.Y()) * 1024.0 ) / double(aOldFont.GetHeight()));
489                 rPoly[n] = aPoint;
490             }
491             Writer::Impl_addPolygon( maGlyphData, rPoly, true );
492         }
493     }
494     Writer::Impl_addEndShapeRecord( maGlyphData );
495 
496     maGlyphData.pad();
497 
498     pVDev->SetFont( aOldFont );
499 
500     return mnNextIndex++;
501 }
502 
503 // -----------------------------------------------------------------------------
504 
write(SvStream & out)505 void FlashFont::write( SvStream& out )
506 {
507     Tag aTag( TAG_DEFINEFONT );
508 
509     aTag.addUI16( mnId );
510 
511     sal_uInt16 nGlyphs = _uInt16( maGlyphOffsets.size() );
512     sal_uInt16 nOffset = nGlyphs * sizeof( sal_uInt16 );
513 
514     for(vector< sal_uInt16 >::iterator i = maGlyphOffsets.begin(); i != maGlyphOffsets.end(); i++)
515         aTag.addUI16( nOffset + (*i) );
516 
517     aTag.addBits( maGlyphData );
518 
519     aTag.write( out );
520 }
521 
522 ////////////////////////////////////////////////////////////////////////////////
523 
524 /** this c'tor creates a solid fill style */
FillStyle(const Color & rSolidColor)525 FillStyle::FillStyle( const Color& rSolidColor )
526 :   meType( solid ),
527     maColor( rSolidColor )
528 {
529 }
530 
531 // -----------------------------------------------------------------------------
532 
533 /** this c'tor creates a tiled or clipped bitmap fill style */
FillStyle(sal_uInt16 nBitmapId,bool bClipped,const::basegfx::B2DHomMatrix & rMatrix)534 FillStyle::FillStyle( sal_uInt16 nBitmapId, bool bClipped, const ::basegfx::B2DHomMatrix& rMatrix ) // #i73264#
535 :   meType( bClipped ? clipped_bitmap : tiled_bitmap ),
536     maMatrix( rMatrix ),
537     mnBitmapId( nBitmapId )
538 {
539 }
540 
541 // -----------------------------------------------------------------------------
542 
Impl_getFillStyleType(const Gradient & rGradient)543 FillStyle::FillStyleType Impl_getFillStyleType( const Gradient& rGradient )
544 {
545     switch( rGradient.GetStyle() )
546     {
547     case GradientStyle_ELLIPTICAL:
548     case GradientStyle_RADIAL:
549         return FillStyle::radial_gradient;
550 //  case GradientStyle_AXIAL:
551 //  case GradientStyle_SQUARE:
552 //  case GradientStyle_RECT:
553 //  case GradientStyle_LINEAR:
554     default:
555         return FillStyle::linear_gradient;
556     }
557 }
558 
559 // -----------------------------------------------------------------------------
560 
561 /** this c'tor creates a linear or radial gradient fill style */
FillStyle(const Rectangle & rBoundRect,const Gradient & rGradient)562 FillStyle::FillStyle( const Rectangle& rBoundRect, const Gradient& rGradient )
563 :   meType( Impl_getFillStyleType( rGradient ) ),
564     maGradient( rGradient ),
565     maBoundRect( rBoundRect )
566 {
567 }
568 
569 // -----------------------------------------------------------------------------
570 
addTo(Tag * pTag) const571 void FillStyle::addTo( Tag* pTag ) const
572 {
573     pTag->addUI8( sal::static_int_cast<sal_uInt8>( meType ) );
574     switch( meType )
575     {
576     case solid:
577         pTag->addRGBA( maColor );
578         break;
579     case linear_gradient:
580     case radial_gradient:
581         Impl_addGradient( pTag );
582         break;
583     case tiled_bitmap:
584     case clipped_bitmap:
585         pTag->addUI16( mnBitmapId );
586         pTag->addMatrix( maMatrix );
587         break;
588     }
589 }
590 
591 // -----------------------------------------------------------------------------
592 
593 struct GradRecord
594 {
595     sal_uInt8   mnRatio;
596     Color       maColor;
597 
GradRecordGradRecord598     GradRecord( sal_uInt8 nRatio, const Color& rColor ) : mnRatio( nRatio ), maColor( rColor ) {}
599 };
600 
601 // TODO: better emulation of our gradients
Impl_addGradient(Tag * pTag) const602 void FillStyle::Impl_addGradient( Tag* pTag ) const
603 {
604     vector< struct GradRecord > aGradientRecords;
605     basegfx::B2DHomMatrix m(basegfx::tools::createRotateB2DHomMatrix((maGradient.GetAngle() - 900) * F_PI1800));
606 
607     switch( maGradient.GetStyle() )
608     {
609     case GradientStyle_ELLIPTICAL:
610     case GradientStyle_RADIAL:
611         {
612             aGradientRecords.push_back( GradRecord( 0x00, maGradient.GetEndColor() ) );
613             aGradientRecords.push_back( GradRecord( 0xff, maGradient.GetStartColor() ) );
614 
615             double tx = ( maGradient.GetOfsX() * 32768.0 ) / 100.0;
616             double ty = ( maGradient.GetOfsY() * 32768.0 ) / 100.0;
617             double scalex = (double)maBoundRect.GetWidth() / 32768.0;
618             double scaley = (double)maBoundRect.GetHeight() / 32768.0;
619 
620             m.scale( 1.2, 1.2 );
621 
622             if( scalex > scaley )
623             {
624                 double scale_move = scaley / scalex;
625 
626                 m.translate( tx, scale_move * ty );
627 
628 
629                 m.scale( scalex, scalex );
630             }
631             else
632             {
633                 double scale_move = scalex / scaley;
634 
635                 m.translate( scale_move * tx, ty );
636 
637 
638                 m.scale( scaley, scaley );
639             }
640 
641         }
642         break;
643     case GradientStyle_AXIAL:
644         {
645             aGradientRecords.push_back( GradRecord( 0x00, maGradient.GetEndColor() ) );
646             aGradientRecords.push_back( GradRecord( 0x80, maGradient.GetStartColor() ) );
647             aGradientRecords.push_back( GradRecord( 0xff, maGradient.GetEndColor() ) );
648             double tx = ( 32768.0 / 2.0 );
649             double ty = ( 32768.0 / 2.0 );
650             double scalex = (double)maBoundRect.GetWidth() / 32768.0;
651             double scaley = (double)maBoundRect.GetHeight() / 32768.0;
652 
653             m.translate( tx, ty );
654             m.scale( scalex, scaley );
655         }
656         break;
657     case GradientStyle_SQUARE:
658     case GradientStyle_RECT:
659     case GradientStyle_LINEAR:
660         {
661             aGradientRecords.push_back( GradRecord( 0x00, maGradient.GetStartColor() ) );
662             aGradientRecords.push_back( GradRecord( 0xff, maGradient.GetEndColor() ) );
663             double scalex = (double)maBoundRect.GetWidth() / 32768.0;
664             double scaley = (double)maBoundRect.GetHeight() / 32768.0;
665 
666             m.scale( scalex, scaley );
667 
668             m.translate( maBoundRect.GetWidth() / 2.0, maBoundRect.GetHeight() / 2.0 );
669         }
670         break;
671     case  GradientStyle_FORCE_EQUAL_SIZE: break;
672     }
673 
674     m.translate( maBoundRect.nLeft, maBoundRect.nTop );
675 
676     pTag->addMatrix( m );
677 
678     DBG_ASSERT( aGradientRecords.size() < 8, "Illegal FlashGradient!" );
679 
680     pTag->addUI8( static_cast<sal_uInt8>( aGradientRecords.size() ) );
681 
682     for(std::vector< GradRecord >::iterator i = aGradientRecords.begin(); i != aGradientRecords.end(); i++)
683     {
684         pTag->addUI8( (*i).mnRatio );
685         pTag->addRGBA( (*i).maColor );
686     }
687 }
688 
689