1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 
21 #include <tools/stream.hxx>
22 #include <vcl/graph.hxx>
23 #include <vcl/outdev.hxx>
24 #include <vcl/BitmapReadAccess.hxx>
25 #include <vcl/FilterConfigItem.hxx>
26 #include <com/sun/star/task/XStatusIndicator.hpp>
27 #include <filter/TiffWriter.hxx>
28 
29 #define NewSubfileType              254
30 #define ImageWidth                  256
31 #define ImageLength                 257
32 #define BitsPerSample               258
33 #define Compression                 259
34 #define PhotometricInterpretation   262
35 #define StripOffsets                273
36 #define SamplesPerPixel             277
37 #define RowsPerStrip                278
38 #define StripByteCounts             279
39 #define XResolution                 282
40 #define YResolution                 283
41 #define PlanarConfiguration         284
42 #define ResolutionUnit              296
43 #define ColorMap                    320
44 
45 namespace {
46 
47 struct TIFFLZWCTreeNode
48 {
49 
50     TIFFLZWCTreeNode*       pBrother;       // next node with the same father
51     TIFFLZWCTreeNode*       pFirstChild;    // first son
52     sal_uInt16              nCode;          // The code for the string of pixel values, which arises if... <missing comment>
53     sal_uInt16              nValue;         // pixel value
54 };
55 
56 
57 class TIFFWriter
58 {
59 private:
60 
61     SvStream& m_rOStm;
62     sal_uInt32              mnStreamOfs;
63 
64     bool                    mbStatus;
65     BitmapReadAccess*       mpAcc;
66 
67     sal_uInt32              mnWidth, mnHeight, mnColors;
68     sal_uInt32              mnCurAllPictHeight;
69     sal_uInt32              mnSumOfAllPictHeight;
70     sal_uInt32              mnBitsPerPixel;
71     sal_uInt32              mnLastPercent;
72 
73     sal_uInt32              mnLatestIfdPos;
74     sal_uInt16              mnTagCount;                 // number of tags already written
75     sal_uInt32              mnCurrentTagCountPos;       // offset to the position where the current
76                                                         // tag count is to insert
77 
78     sal_uInt32              mnXResPos;                  // if != 0 this DWORDs stores the
79     sal_uInt32              mnYResPos;                  // actual streamposition of the
80     sal_uInt32              mnPalPos;                   // Tag Entry
81     sal_uInt32              mnBitmapPos;
82     sal_uInt32              mnStripByteCountPos;
83 
84     std::unique_ptr<TIFFLZWCTreeNode[]> pTable;
85     TIFFLZWCTreeNode*       pPrefix;
86     sal_uInt16              nDataSize;
87     sal_uInt16              nClearCode;
88     sal_uInt16              nEOICode;
89     sal_uInt16              nTableSize;
90     sal_uInt16              nCodeSize;
91     sal_uInt32              nOffset;
92     sal_uInt32              dwShift;
93 
94     css::uno::Reference< css::task::XStatusIndicator > xStatusIndicator;
95 
96     void                ImplCallback( sal_uInt32 nPercent );
97     bool                ImplWriteHeader( bool bMultiPage );
98     void                ImplWritePalette();
99     void                ImplWriteBody();
100     void                ImplWriteTag( sal_uInt16 TagID, sal_uInt16 DataType, sal_uInt32 NumberOfItems, sal_uInt32 Value);
101     void                ImplWriteResolution( sal_uInt64 nStreamPos, sal_uInt32 nResolutionUnit );
102     void                StartCompression();
103     void                Compress( sal_uInt8 nSrc );
104     void                EndCompression();
105     inline void         WriteBits( sal_uInt16 nCode, sal_uInt16 nCodeLen );
106 
107 public:
108 
109     explicit            TIFFWriter(SvStream &rStream);
110 
111     bool WriteTIFF( const Graphic& rGraphic, FilterConfigItem const * pFilterConfigItem );
112 };
113 
114 }
115 
TIFFWriter(SvStream & rStream)116 TIFFWriter::TIFFWriter(SvStream &rStream)
117     : m_rOStm(rStream)
118     , mnStreamOfs(0)
119     , mbStatus(true)
120     , mpAcc(nullptr)
121     , mnWidth(0)
122     , mnHeight(0)
123     , mnColors(0)
124     , mnCurAllPictHeight(0)
125     , mnSumOfAllPictHeight(0)
126     , mnBitsPerPixel(0)
127     , mnLastPercent(0)
128     , mnLatestIfdPos(0)
129     , mnTagCount(0)
130     , mnCurrentTagCountPos(0)
131     , mnXResPos(0)
132     , mnYResPos(0)
133     , mnPalPos(0)
134     , mnBitmapPos(0)
135     , mnStripByteCountPos(0)
136     , pPrefix(nullptr)
137     , nDataSize(0)
138     , nClearCode(0)
139     , nEOICode(0)
140     , nTableSize(0)
141     , nCodeSize(0)
142     , nOffset(0)
143     , dwShift(0)
144 {
145 }
146 
147 
WriteTIFF(const Graphic & rGraphic,FilterConfigItem const * pFilterConfigItem)148 bool TIFFWriter::WriteTIFF( const Graphic& rGraphic, FilterConfigItem const * pFilterConfigItem)
149 {
150     if ( pFilterConfigItem )
151     {
152         xStatusIndicator = pFilterConfigItem->GetStatusIndicator();
153         if ( xStatusIndicator.is() )
154         {
155             xStatusIndicator->start( OUString(), 100 );
156         }
157     }
158 
159     const SvStreamEndian nOldFormat = m_rOStm.GetEndian();
160     mnStreamOfs = m_rOStm.Tell();
161 
162     // we will use the BIG Endian Mode
163     // TIFF header
164     m_rOStm.SetEndian( SvStreamEndian::BIG );
165     m_rOStm.WriteUInt32( 0x4d4d002a );      // TIFF identifier
166     mnLatestIfdPos = m_rOStm.Tell();
167     m_rOStm.WriteUInt32( 0 );
168 
169     if( mbStatus )
170     {
171         Animation aAnimation = rGraphic.IsAnimated() ? rGraphic.GetAnimation() : Animation();
172         if (!rGraphic.IsAnimated())
173             aAnimation.Insert(AnimationBitmap(rGraphic.GetBitmapEx(), Point(), Size()));
174 
175         for (size_t i = 0; i < aAnimation.Count(); ++i)
176             mnSumOfAllPictHeight += aAnimation.Get(i).maBitmapEx.GetSizePixel().Height();
177 
178         for (size_t i = 0; mbStatus && i < aAnimation.Count(); ++i)
179         {
180             mnPalPos = 0;
181             const AnimationBitmap& rAnimationBitmap = aAnimation.Get( i );
182             Bitmap aBmp = rAnimationBitmap.maBitmapEx.GetBitmap();
183             mpAcc = aBmp.AcquireReadAccess();
184             if ( mpAcc )
185             {
186                 mnBitsPerPixel = vcl::pixelFormatBitCount(aBmp.getPixelFormat());
187 
188                 // export code below only handles four discrete cases
189                 mnBitsPerPixel =
190                     mnBitsPerPixel <= 1 ? 1 : mnBitsPerPixel <= 4 ? 4 : mnBitsPerPixel <= 8 ? 8 : 24;
191 
192                 if ( ImplWriteHeader( aAnimation.Count() > 0 ) )
193                 {
194                     Size aDestMapSize( 300, 300 );
195                     const MapMode& aMapMode( aBmp.GetPrefMapMode() );
196                     if ( aMapMode.GetMapUnit() != MapUnit::MapPixel )
197                     {
198                         const Size aPrefSize( rGraphic.GetPrefSize() );
199                         aDestMapSize = OutputDevice::LogicToLogic(aPrefSize, aMapMode, MapMode(MapUnit::MapInch));
200                     }
201                     ImplWriteResolution( mnXResPos, aDestMapSize.Width() );
202                     ImplWriteResolution( mnYResPos, aDestMapSize.Height() );
203                     if  ( mnPalPos )
204                         ImplWritePalette();
205                     ImplWriteBody();
206                 }
207                 sal_uInt32 nCurPos = m_rOStm.Tell();
208                 m_rOStm.Seek( mnCurrentTagCountPos );
209                 m_rOStm.WriteUInt16( mnTagCount );
210                 m_rOStm.Seek( nCurPos );
211 
212                 Bitmap::ReleaseAccess( mpAcc );
213             }
214             else
215                 mbStatus = false;
216         }
217     }
218     m_rOStm.SetEndian( nOldFormat );
219 
220     if ( xStatusIndicator.is() )
221         xStatusIndicator->end();
222 
223     return mbStatus;
224 }
225 
226 
ImplCallback(sal_uInt32 nPercent)227 void TIFFWriter::ImplCallback( sal_uInt32 nPercent )
228 {
229     if ( xStatusIndicator.is() )
230     {
231         if( nPercent >= mnLastPercent + 3 )
232         {
233             mnLastPercent = nPercent;
234             if ( nPercent <= 100 )
235                 xStatusIndicator->setValue( nPercent );
236         }
237     }
238 }
239 
240 
ImplWriteHeader(bool bMultiPage)241 bool TIFFWriter::ImplWriteHeader( bool bMultiPage )
242 {
243     mnTagCount = 0;
244     mnWidth = mpAcc->Width();
245     mnHeight = mpAcc->Height();
246 
247     if ( mnWidth && mnHeight && mnBitsPerPixel && mbStatus )
248     {
249         sal_uInt32 nCurrentPos = m_rOStm.Tell();
250         m_rOStm.Seek( mnLatestIfdPos );
251         m_rOStm.WriteUInt32( nCurrentPos - mnStreamOfs );   // offset to the IFD
252         m_rOStm.Seek( nCurrentPos );
253 
254         // (OFS8) TIFF image file directory (IFD)
255         mnCurrentTagCountPos = m_rOStm.Tell();
256         m_rOStm.WriteUInt16( 0 );               // the number of tangents to insert later
257 
258         sal_uInt32 nSubFileFlags = 0;
259         if ( bMultiPage )
260             nSubFileFlags |= 2;
261         ImplWriteTag( NewSubfileType, 4, 1, nSubFileFlags );
262         ImplWriteTag( ImageWidth, 4, 1, mnWidth );
263         ImplWriteTag( ImageLength, 4, 1, mnHeight);
264         ImplWriteTag( BitsPerSample, 3, 1, ( mnBitsPerPixel == 24 ) ? 8 : mnBitsPerPixel );
265         ImplWriteTag( Compression, 3, 1, 5 );
266         sal_uInt8 nTemp;
267         switch ( mnBitsPerPixel )
268         {
269             case 1 :
270                 nTemp = 1;
271                 break;
272             case 4 :
273             case 8 :
274                 nTemp = 3;
275                 break;
276             case 24:
277                 nTemp = 2;
278                 break;
279             default:
280                 nTemp = 0;  // -Wall set a default...
281                 break;
282         }
283         ImplWriteTag( PhotometricInterpretation, 3, 1, nTemp );
284         mnBitmapPos = m_rOStm.Tell();
285         ImplWriteTag( StripOffsets, 4, 1, 0 );
286         ImplWriteTag( SamplesPerPixel, 3, 1, ( mnBitsPerPixel == 24 ) ? 3 : 1 );
287         ImplWriteTag( RowsPerStrip, 4, 1, mnHeight );
288         mnStripByteCountPos = m_rOStm.Tell();
289         ImplWriteTag( StripByteCounts, 4, 1, ( ( mnWidth * mnBitsPerPixel * mnHeight ) + 7 ) >> 3 );
290         mnXResPos = m_rOStm.Tell();
291         ImplWriteTag( XResolution, 5, 1, 0 );
292         mnYResPos = m_rOStm.Tell();
293         ImplWriteTag( YResolution, 5, 1, 0 );
294         if ( mnBitsPerPixel != 1 )
295             ImplWriteTag( PlanarConfiguration, 3, 1, 1 );   //  ( RGB ORDER )
296         ImplWriteTag( ResolutionUnit, 3, 1, 2);             // Resolution Unit is Inch
297         if ( ( mnBitsPerPixel == 4 ) || ( mnBitsPerPixel == 8 ) )
298         {
299             mnColors = mpAcc->GetPaletteEntryCount();
300             mnPalPos = m_rOStm.Tell();
301             ImplWriteTag( ColorMap, 3, 3 * mnColors, 0 );
302         }
303 
304         // and last we write zero to close the num dir entries list
305         mnLatestIfdPos = m_rOStm.Tell();
306         m_rOStm.WriteUInt32( 0 );               // there are no more IFD
307     }
308     else
309         mbStatus = false;
310 
311     return mbStatus;
312 }
313 
314 
ImplWritePalette()315 void TIFFWriter::ImplWritePalette()
316 {
317     sal_uInt64 nCurrentPos = m_rOStm.Tell();
318     m_rOStm.Seek( mnPalPos + 8 );           // the palette tag entry needs the offset
319     m_rOStm.WriteUInt32( nCurrentPos - mnStreamOfs );  // to the palette colors
320     m_rOStm.Seek( nCurrentPos );
321 
322     for ( sal_uInt32 i = 0; i < mnColors; i++ )
323     {
324         const BitmapColor& rColor = mpAcc->GetPaletteColor( i );
325         m_rOStm.WriteUInt16( rColor.GetRed() << 8 );
326     }
327     for ( sal_uInt32 i = 0; i < mnColors; i++ )
328     {
329         const BitmapColor& rColor = mpAcc->GetPaletteColor( i );
330         m_rOStm.WriteUInt16( rColor.GetGreen() << 8 );
331     }
332     for ( sal_uInt32 i = 0; i < mnColors; i++ )
333     {
334         const BitmapColor& rColor = mpAcc->GetPaletteColor( i );
335         m_rOStm.WriteUInt16( rColor.GetBlue() << 8 );
336     }
337 }
338 
339 
ImplWriteBody()340 void TIFFWriter::ImplWriteBody()
341 {
342     sal_uInt8   nTemp = 0;
343     sal_uInt8    nShift;
344     sal_uInt32   j, x, y;
345 
346     sal_uInt64 nGfxBegin = m_rOStm.Tell();
347     m_rOStm.Seek( mnBitmapPos + 8 );                // the strip offset tag entry needs the offset
348     m_rOStm.WriteUInt32( nGfxBegin - mnStreamOfs ); // to the bitmap data
349     m_rOStm.Seek( nGfxBegin );
350 
351     StartCompression();
352 
353     switch( mnBitsPerPixel )
354     {
355         case 24 :
356         {
357             for ( y = 0; y < mnHeight; y++, mnCurAllPictHeight++ )
358             {
359                 ImplCallback( 100 * mnCurAllPictHeight / mnSumOfAllPictHeight );
360                 Scanline pScanline = mpAcc->GetScanline( y );
361                 for ( x = 0; x < mnWidth; x++ )
362                 {
363                     const BitmapColor& rColor = mpAcc->GetPixelFromData( pScanline, x );
364                     Compress( rColor.GetRed() );
365                     Compress( rColor.GetGreen() );
366                     Compress( rColor.GetBlue() );
367                 }
368             }
369         }
370         break;
371 
372         case 8 :
373         {
374             for ( y = 0; y < mnHeight; y++, mnCurAllPictHeight++ )
375             {
376                 ImplCallback( 100 * mnCurAllPictHeight / mnSumOfAllPictHeight );
377                 Scanline pScanline = mpAcc->GetScanline( y );
378                 for ( x = 0; x < mnWidth; x++ )
379                 {
380                     Compress( mpAcc->GetIndexFromData( pScanline, x ) );
381                 }
382             }
383         }
384         break;
385 
386         case 4 :
387         {
388             for ( nShift = 0, y = 0; y < mnHeight; y++, mnCurAllPictHeight++ )
389             {
390                 ImplCallback( 100 * mnCurAllPictHeight / mnSumOfAllPictHeight );
391                 Scanline pScanline = mpAcc->GetScanline( y );
392                 for ( x = 0; x < mnWidth; x++, nShift++ )
393                 {
394                     if (!( nShift & 1 ))
395                         nTemp = ( mpAcc->GetIndexFromData( pScanline, x ) << 4 );
396                     else
397                         Compress( static_cast<sal_uInt8>( nTemp | ( mpAcc->GetIndexFromData( pScanline, x ) & 0xf ) ) );
398                 }
399                 if ( nShift & 1 )
400                     Compress( nTemp );
401             }
402         }
403         break;
404 
405         case 1 :
406         {
407             j = 1;
408             for ( y = 0; y < mnHeight; y++, mnCurAllPictHeight++ )
409             {
410                 ImplCallback( 100 * mnCurAllPictHeight / mnSumOfAllPictHeight );
411                 Scanline pScanline = mpAcc->GetScanline( y );
412                 for ( x = 0; x < mnWidth; x++)
413                 {
414                     j <<= 1;
415                     j |= ( ( ~mpAcc->GetIndexFromData( pScanline, x ) ) & 1 );
416                     if ( j & 0x100 )
417                     {
418                         Compress( static_cast<sal_uInt8>(j) );
419                         j = 1;
420                     }
421                 }
422                 if ( j != 1 )
423                 {
424                     Compress( static_cast<sal_uInt8>(j << ( ( ( x & 7) ^ 7 ) + 1 ) ) );
425                     j = 1;
426                 }
427             }
428         }
429         break;
430 
431         default:
432         {
433             mbStatus = false;
434         }
435         break;
436     }
437 
438     EndCompression();
439 
440     if ( mnStripByteCountPos && mbStatus )
441     {
442         sal_uInt64 nGfxEnd = m_rOStm.Tell();
443         m_rOStm.Seek( mnStripByteCountPos + 8 );
444         m_rOStm.WriteUInt32( nGfxEnd - nGfxBegin );      // mnStripByteCountPos needs the size of the compression data
445         m_rOStm.Seek( nGfxEnd );
446     }
447 }
448 
449 
ImplWriteResolution(sal_uInt64 nStreamPos,sal_uInt32 nResolutionUnit)450 void TIFFWriter::ImplWriteResolution( sal_uInt64 nStreamPos, sal_uInt32 nResolutionUnit )
451 {
452     sal_uInt64 nCurrentPos = m_rOStm.Tell();
453     m_rOStm.Seek( nStreamPos + 8 );
454     m_rOStm.WriteUInt32( nCurrentPos - mnStreamOfs );
455     m_rOStm.Seek( nCurrentPos );
456     m_rOStm.WriteUInt32( 1 );
457     m_rOStm.WriteUInt32( nResolutionUnit );
458 }
459 
460 
ImplWriteTag(sal_uInt16 nTagID,sal_uInt16 nDataType,sal_uInt32 nNumberOfItems,sal_uInt32 nValue)461 void TIFFWriter::ImplWriteTag( sal_uInt16 nTagID, sal_uInt16 nDataType, sal_uInt32 nNumberOfItems, sal_uInt32 nValue)
462 {
463         mnTagCount++;
464 
465         m_rOStm.WriteUInt16( nTagID );
466         m_rOStm.WriteUInt16( nDataType );
467         m_rOStm.WriteUInt32( nNumberOfItems );
468         if ( nDataType == 3 )
469             nValue <<=16;           // in Big Endian Mode WORDS needed to be shifted to a DWORD
470         m_rOStm.WriteUInt32( nValue );
471 }
472 
473 
WriteBits(sal_uInt16 nCode,sal_uInt16 nCodeLen)474 inline void TIFFWriter::WriteBits( sal_uInt16 nCode, sal_uInt16 nCodeLen )
475 {
476     dwShift |= ( nCode << ( nOffset - nCodeLen ) );
477     nOffset -= nCodeLen;
478     while ( nOffset < 24 )
479     {
480         m_rOStm.WriteUChar( dwShift >> 24 );
481         dwShift <<= 8;
482         nOffset += 8;
483     }
484     if ( nCode == 257 && nOffset != 32 )
485     {
486         m_rOStm.WriteUChar( dwShift >> 24 );
487     }
488 }
489 
490 
StartCompression()491 void TIFFWriter::StartCompression()
492 {
493     sal_uInt16 i;
494     nDataSize = 8;
495 
496     nClearCode = 1 << nDataSize;
497     nEOICode = nClearCode + 1;
498     nTableSize = nEOICode + 1;
499     nCodeSize = nDataSize + 1;
500 
501     nOffset = 32;                       // number of free bits in dwShift
502     dwShift = 0;
503 
504     pTable.reset(new TIFFLZWCTreeNode[ 4096 ]);
505 
506     for ( i = 0; i < 4096; i++)
507     {
508         pTable[ i ].pBrother = pTable[ i ].pFirstChild = nullptr;
509         pTable[ i ].nCode = i;
510         pTable[ i ].nValue = static_cast<sal_uInt8>( i );
511     }
512 
513     pPrefix = nullptr;
514     WriteBits( nClearCode, nCodeSize );
515 }
516 
517 
Compress(sal_uInt8 nCompThis)518 void TIFFWriter::Compress( sal_uInt8 nCompThis )
519 {
520     TIFFLZWCTreeNode*    p;
521     sal_uInt16              i;
522     sal_uInt8               nV;
523 
524     if( !pPrefix )
525     {
526         pPrefix = &pTable[nCompThis];
527     }
528     else
529     {
530         nV = nCompThis;
531         for( p = pPrefix->pFirstChild; p != nullptr; p = p->pBrother )
532         {
533             if ( p->nValue == nV )
534                 break;
535         }
536 
537         if( p )
538             pPrefix = p;
539         else
540         {
541             WriteBits( pPrefix->nCode, nCodeSize );
542 
543             if ( nTableSize == 409 )
544             {
545                 WriteBits( nClearCode, nCodeSize );
546 
547                 for ( i = 0; i < nClearCode; i++ )
548                     pTable[ i ].pFirstChild = nullptr;
549 
550                 nCodeSize = nDataSize + 1;
551                 nTableSize = nEOICode + 1;
552             }
553             else
554             {
555                 if( nTableSize == static_cast<sal_uInt16>( ( 1 << nCodeSize ) - 1 ) )
556                     nCodeSize++;
557 
558                 p = &pTable[ nTableSize++ ];
559                 p->pBrother = pPrefix->pFirstChild;
560                 pPrefix->pFirstChild = p;
561                 p->nValue = nV;
562                 p->pFirstChild = nullptr;
563             }
564 
565             pPrefix = &pTable[nV];
566         }
567     }
568 }
569 
570 
EndCompression()571 void TIFFWriter::EndCompression()
572 {
573     if( pPrefix )
574         WriteBits( pPrefix->nCode, nCodeSize );
575 
576     WriteBits( nEOICode, nCodeSize );
577     pTable.reset();
578 }
579 
ExportTiffGraphicImport(SvStream & rStream,Graphic & rGraphic,FilterConfigItem * pFilterConfigItem)580 bool ExportTiffGraphicImport(SvStream & rStream, Graphic & rGraphic, FilterConfigItem* pFilterConfigItem)
581 {
582     TIFFWriter aWriter(rStream);
583     return aWriter.WriteTIFF( rGraphic, pFilterConfigItem );
584 }
585 
586 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
587