LibreOffice Module vcl (master) 1
xpmread.cxx
Go to the documentation of this file.
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#include <filter/XpmReader.hxx>
21
22#include <vcl/graph.hxx>
23#include <tools/stream.hxx>
24
27
28#include "rgbtable.hxx"
29
30#include <cstring>
31#include <array>
32#include <map>
33
34#define XPMTEMPBUFSIZE 0x00008000
35#define XPMSTRINGBUF 0x00008000
36
37#define XPMIDENTIFIER 0x00000001 // mnIdentifier includes one of the six phases
38#define XPMDEFINITION 0x00000002 // the XPM format consists of
39#define XPMVALUES 0x00000003
40#define XPMCOLORS 0x00000004
41#define XPMPIXELS 0x00000005
42#define XPMEXTENSIONS 0x00000006
43#define XPMENDEXT 0x00000007
44
45#define XPMREMARK 0x00000001 // defines used by mnStatus
46#define XPMDOUBLE 0x00000002
47#define XPMSTRING 0x00000004
48#define XPMFINISHED 0x00000008
49
50namespace {
51
52enum ReadState
53{
54 XPMREAD_OK,
55 XPMREAD_ERROR,
56 XPMREAD_NEED_MORE
57};
58
59}
60
62class Graphic;
63
64namespace {
65
66class XPMReader : public GraphicReader
67{
68private:
69
70 SvStream& mrIStm;
71 Bitmap maBmp;
73 Bitmap maMaskBmp;
75 tools::Long mnLastPos;
76
79 sal_uLong mnColors;
80 sal_uInt32 mnCpp; // characters per pix
81 bool mbTransparent;
82 bool mbStatus;
83 sal_uLong mnStatus;
84 sal_uLong mnIdentifier;
85 sal_uInt8 mcThisByte;
86 sal_uInt8 mcLastByte;
87 sal_uLong mnTempAvail;
88 sal_uInt8* mpTempBuf;
89 sal_uInt8* mpTempPtr;
90 // each key is ( mnCpp )Byte(s)-> ASCII entry assigned to the colour
91 // each colordata is
92 // 1 Byte -> 0xFF if colour is transparent
93 // 3 Bytes -> RGB value of the colour
94 typedef std::array<sal_uInt8,4> colordata;
95 typedef std::map<OString, colordata> colormap;
96 colormap maColMap;
97 sal_uLong mnStringSize;
98 sal_uInt8* mpStringBuf;
99 sal_uLong mnParaSize;
100 sal_uInt8* mpPara;
101
102 bool ImplGetString();
103 bool ImplGetColor();
104 bool ImplGetScanLine( sal_uLong );
105 bool ImplGetColSub(colordata &rDest);
106 bool ImplGetColKey( sal_uInt8 );
107 void ImplGetRGBHex(colordata &rDest, sal_uLong);
108 bool ImplGetPara( sal_uLong numb );
109 static bool ImplCompare(sal_uInt8 const *, sal_uInt8 const *, sal_uLong);
110 sal_uLong ImplGetULONG( sal_uLong nPara );
111
112public:
113 explicit XPMReader( SvStream& rStm );
114
115 ReadState ReadXPM( Graphic& rGraphic );
116};
117
118}
119
120XPMReader::XPMReader(SvStream& rStm)
121 : mrIStm(rStm)
122 , mnLastPos(rStm.Tell())
123 , mnWidth(0)
124 , mnHeight(0)
125 , mnColors(0)
126 , mnCpp(0)
127 , mbTransparent(false)
128 , mbStatus(true)
129 , mnStatus( 0 )
130 , mnIdentifier(XPMIDENTIFIER)
131 , mcThisByte(0)
132 , mcLastByte(0)
133 , mnTempAvail(0)
134 , mpTempBuf(nullptr)
135 , mpTempPtr(nullptr)
136 , mnStringSize(0)
137 , mpStringBuf(nullptr)
138 , mnParaSize(0)
139 , mpPara(nullptr)
140{
141}
142
143ReadState XPMReader::ReadXPM( Graphic& rGraphic )
144{
145 ReadState eReadState;
146 sal_uInt8 cDummy;
147
148 // check if we can real ALL
149 mrIStm.Seek( STREAM_SEEK_TO_END );
150 mrIStm.ReadUChar( cDummy );
151
152 // if we could not read all
153 // return and wait for new data
154 if ( mrIStm.GetError() != ERRCODE_IO_PENDING )
155 {
156 mrIStm.Seek( mnLastPos );
157 mbStatus = true;
158
159 if ( mbStatus )
160 {
161 mpStringBuf = new sal_uInt8 [ XPMSTRINGBUF ];
162 mpTempBuf = new sal_uInt8 [ XPMTEMPBUFSIZE ];
163
164 mbStatus = ImplGetString();
165 if ( mbStatus )
166 {
167 mnIdentifier = XPMVALUES; // fetch Bitmap information
168 mnWidth = ImplGetULONG( 0 );
169 mnHeight = ImplGetULONG( 1 );
170 mnColors = ImplGetULONG( 2 );
171 mnCpp = ImplGetULONG( 3 );
172 }
173 if ( mnColors > ( SAL_MAX_UINT32 / ( 4 + mnCpp ) ) )
174 mbStatus = false;
175 if ( ( mnWidth * mnCpp ) >= XPMSTRINGBUF )
176 mbStatus = false;
177 //xpms are a minimum of one character (one byte) per pixel, so if the file isn't
178 //even that long, it's not all there
179 if (mrIStm.remainingSize() + mnTempAvail < static_cast<sal_uInt64>(mnWidth) * mnHeight)
180 mbStatus = false;
181 if ( mbStatus && mnWidth && mnHeight && mnColors && mnCpp )
182 {
183 mnIdentifier = XPMCOLORS;
184
185 for (sal_uLong i = 0; i < mnColors; ++i)
186 {
187 if (!ImplGetColor())
188 {
189 mbStatus = false;
190 break;
191 }
192 }
193
194 if ( mbStatus )
195 {
196 // create a 24bit graphic when more as 256 colours present
197 auto ePixelFormat = vcl::PixelFormat::INVALID;
198 if ( mnColors > 256 )
199 ePixelFormat = vcl::PixelFormat::N24_BPP;
200 else if ( mnColors > 2 )
201 ePixelFormat = vcl::PixelFormat::N8_BPP;
202 else
203 ePixelFormat = vcl::PixelFormat::N1_BPP;
204
205 maBmp = Bitmap(Size(mnWidth, mnHeight), ePixelFormat);
206 mpAcc = BitmapScopedWriteAccess(maBmp);
207
208 // mbTransparent is TRUE if at least one colour is transparent
209 if ( mbTransparent )
210 {
211 maMaskBmp = Bitmap(Size(mnWidth, mnHeight), vcl::PixelFormat::N1_BPP);
212 mpMaskAcc = BitmapScopedWriteAccess(maMaskBmp);
213 if ( !mpMaskAcc )
214 mbStatus = false;
215 }
216 if( mpAcc && mbStatus )
217 {
218 if (mnColors <= 256) // palette is only needed by using less than 257
219 { // colors
220 sal_uInt8 i = 0;
221 for (auto& elem : maColMap)
222 {
223 mpAcc->SetPaletteColor(i, Color(elem.second[1], elem.second[2], elem.second[3]));
224 //reuse map entry, overwrite color with palette index
225 elem.second[1] = i;
226 i++;
227 }
228 }
229
230 // now we get the bitmap data
231 mnIdentifier = XPMPIXELS;
232 for (sal_uLong i = 0; i < mnHeight; ++i)
233 {
234 if ( !ImplGetScanLine( i ) )
235 {
236 mbStatus = false;
237 break;
238 }
239 }
240 mnIdentifier = XPMEXTENSIONS;
241 }
242 }
243 }
244
245 delete[] mpStringBuf;
246 delete[] mpTempBuf;
247
248 }
249 if( mbStatus )
250 {
251 mpAcc.reset();
252 if ( mpMaskAcc )
253 {
254 mpMaskAcc.reset();
255 rGraphic = Graphic( BitmapEx( maBmp, maMaskBmp ) );
256 }
257 else
258 {
259 rGraphic = BitmapEx(maBmp);
260 }
261 eReadState = XPMREAD_OK;
262 }
263 else
264 {
265 mpMaskAcc.reset();
266 mpAcc.reset();
267
268 eReadState = XPMREAD_ERROR;
269 }
270 }
271 else
272 {
273 mrIStm.ResetError();
274 eReadState = XPMREAD_NEED_MORE;
275 }
276 return eReadState;
277}
278
279// ImplGetColor returns various colour values,
280// returns TRUE if various colours could be assigned
281bool XPMReader::ImplGetColor()
282{
283 sal_uInt8* pString = mpStringBuf;
284 if (!ImplGetString())
285 return false;
286
287 if (mnStringSize < mnCpp)
288 return false;
289
290 OString aKey(reinterpret_cast<char*>(pString), mnCpp);
291 colordata aValue{0};
292 bool bStatus = ImplGetColSub(aValue);
293 if (bStatus)
294 {
295 maColMap[aKey] = aValue;
296 }
297 return bStatus;
298}
299
300// ImpGetScanLine reads the string mpBufSize and writes the pixel in the
301// Bitmap. Parameter nY is the horizontal position.
302bool XPMReader::ImplGetScanLine( sal_uLong nY )
303{
304 bool bStatus = ImplGetString();
305 sal_uInt8* pString = mpStringBuf;
306 BitmapColor aWhite;
307 BitmapColor aBlack;
308
309 if ( bStatus )
310 {
311 if ( mpMaskAcc )
312 {
313 aWhite = mpMaskAcc->GetBestMatchingColor( COL_WHITE );
314 aBlack = mpMaskAcc->GetBestMatchingColor( COL_BLACK );
315 }
316 if ( mnStringSize != ( mnWidth * mnCpp ))
317 bStatus = false;
318 else
319 {
320 Scanline pScanline = mpAcc->GetScanline(nY);
321 Scanline pMaskScanline = mpMaskAcc ? mpMaskAcc->GetScanline(nY) : nullptr;
322 for (sal_uLong i = 0; i < mnWidth; ++i)
323 {
324 OString aKey(reinterpret_cast<char*>(pString), mnCpp);
325 auto it = maColMap.find(aKey);
326 if (it != maColMap.end())
327 {
328 if (mnColors > 256)
329 mpAcc->SetPixelOnData(pScanline, i, Color(it->second[1], it->second[2], it->second[3]));
330 else
331 mpAcc->SetPixelOnData(pScanline, i, BitmapColor(it->second[1]));
332 if (pMaskScanline)
333 mpMaskAcc->SetPixelOnData(pMaskScanline, i, it->second[0] ? aWhite : aBlack);
334 }
335 pString += mnCpp;
336 }
337 }
338 }
339 return bStatus;
340}
341
342// tries to determine a colour value from mpStringBuf
343// if a colour was found the RGB value is written a pDest[1]..pDest[2]
344// pDest[0] contains 0xFF if the colour is transparent otherwise 0
345
346bool XPMReader::ImplGetColSub(colordata &rDest)
347{
348 unsigned char cTransparent[] = "None";
349
350 bool bColStatus = false;
351
352 if ( ImplGetColKey( 'c' ) || ImplGetColKey( 'm' ) || ImplGetColKey( 'g' ) )
353 {
354 // hexentry for RGB or HSV color ?
355 if (*mpPara == '#')
356 {
357 rDest[0] = 0;
358 bColStatus = true;
359 switch ( mnParaSize )
360 {
361 case 25 :
362 ImplGetRGBHex(rDest, 6);
363 break;
364 case 13 :
365 ImplGetRGBHex(rDest, 2);
366 break;
367 case 7 :
368 ImplGetRGBHex(rDest, 0);
369 break;
370 default:
371 bColStatus = false;
372 break;
373 }
374 }
375 // maybe pixel is transparent
376 else if ( ImplCompare( &cTransparent[0], mpPara, 4 ))
377 {
378 rDest[0] = 0xff;
379 bColStatus = true;
380 mbTransparent = true;
381 }
382 // last we will try to get the colorname
383 else if ( mnParaSize > 2 ) // name must enlarge the minimum size
384 {
385 sal_uLong i = 0;
386 while ( true )
387 {
388 if ( pRGBTable[ i ].name == nullptr )
389 break;
390 if ( std::strlen(pRGBTable[i].name) > mnParaSize &&
391 pRGBTable[ i ].name[ mnParaSize ] == 0 )
392 {
393 if ( ImplCompare ( reinterpret_cast<unsigned char const *>(pRGBTable[ i ].name),
394 mpPara, mnParaSize ) )
395 {
396 bColStatus = true;
397 rDest[0] = 0;
398 rDest[1] = pRGBTable[i].red;
399 rDest[2] = pRGBTable[i].green;
400 rDest[3] = pRGBTable[i].blue;
401 break;
402 }
403 }
404 i++;
405 }
406 }
407 }
408 return bColStatus;
409}
410
411// ImplGetColKey searches string mpStringBuf for a parameter 'nKey'
412// and returns a boolean. (if TRUE mpPara and mnParaSize will be set)
413
414bool XPMReader::ImplGetColKey( sal_uInt8 nKey )
415{
416 sal_uInt8 nTemp, nPrev = ' ';
417
418 if (mnStringSize < mnCpp + 1)
419 return false;
420
421 mpPara = mpStringBuf + mnCpp + 1;
422 mnParaSize = 0;
423
424 while ( *mpPara != 0 )
425 {
426 if ( *mpPara == nKey )
427 {
428 nTemp = *( mpPara + 1 );
429 if ( nTemp == ' ' || nTemp == 0x09 )
430 {
431 if ( nPrev == ' ' || nPrev == 0x09 )
432 break;
433 }
434 }
435 nPrev = *mpPara;
436 mpPara++;
437 }
438 if ( *mpPara )
439 {
440 mpPara++;
441 while ( (*mpPara == ' ') || (*mpPara == 0x09) )
442 {
443 mpPara++;
444 }
445 if ( *mpPara != 0 )
446 {
447 while ( *(mpPara+mnParaSize) != ' ' && *(mpPara+mnParaSize) != 0x09 &&
448 *(mpPara+mnParaSize) != 0 )
449 {
450 mnParaSize++;
451 }
452 }
453 }
454 return mnParaSize != 0;
455}
456
457// ImplGetRGBHex translates the ASCII-Hexadecimalvalue belonging to mpPara
458// in a RGB value and writes this to rDest
459// below formats should be contained in mpPara:
460// if nAdd = 0 : '#12ab12' -> RGB = 0x12, 0xab, 0x12
461// 2 : '#1234abcd1234' " " " "
462// 6 : '#12345678abcdefab12345678' " " " "
463
464void XPMReader::ImplGetRGBHex(colordata &rDest, sal_uLong nAdd)
465{
466 sal_uInt8* pPtr = mpPara+1;
467
468 for (sal_uLong i = 1; i < 4; ++i)
469 {
470 sal_uInt8 nHex = (*pPtr++) - '0';
471 if ( nHex > 9 )
472 nHex = ((nHex - 'A' + '0') & 7) + 10;
473
474 sal_uInt8 nTemp = (*pPtr++) - '0';
475 if ( nTemp > 9 )
476 nTemp = ((nTemp - 'A' + '0') & 7) + 10;
477 nHex = ( nHex << 4 ) + nTemp;
478
479 pPtr += nAdd;
480 rDest[i] = nHex;
481 }
482}
483
484// ImplGetUlong returns the value of a up to 6-digit long ASCII-decimal number.
485
486sal_uLong XPMReader::ImplGetULONG( sal_uLong nPara )
487{
488 if ( ImplGetPara ( nPara ) )
489 {
490 sal_uLong nRetValue = 0;
491 sal_uInt8* pPtr = mpPara;
492
493 if ( ( mnParaSize > 6 ) || ( mnParaSize == 0 ) ) return 0;
494 for ( sal_uLong i = 0; i < mnParaSize; i++ )
495 {
496 sal_uInt8 j = (*pPtr++) - 48;
497 if ( j > 9 ) return 0; // ascii is invalid
498 nRetValue*=10;
499 nRetValue+=j;
500 }
501 return nRetValue;
502 }
503 else return 0;
504}
505
506bool XPMReader::ImplCompare(sal_uInt8 const * pSource, sal_uInt8 const * pDest, sal_uLong nSize)
507{
508 for (sal_uLong i = 0; i < nSize; ++i)
509 {
510 if ( ( pSource[i]&~0x20 ) != ( pDest[i]&~0x20 ) )
511 {
512 return false;
513 }
514 }
515 return true;
516}
517
518// ImplGetPara tries to retrieve nNumb (0...x) parameters from mpStringBuf.
519// Parameters are separated by spaces or tabs.
520// If a parameter was found then the return value is TRUE and mpPara + mnParaSize
521// are set.
522
523bool XPMReader::ImplGetPara ( sal_uLong nNumb )
524{
525 sal_uInt8 nByte;
526 sal_uLong nSize = 0;
527 sal_uInt8* pPtr = mpStringBuf;
528 sal_uLong nCount = 0;
529
530 if ( ( *pPtr != ' ' ) && ( *pPtr != 0x09 ) )
531 {
532 mpPara = pPtr;
533 mnParaSize = 0;
534 nCount = 0;
535 }
536 else
537 {
538 mpPara = nullptr;
539 nCount = 0xffffffff;
540 }
541
542 while ( nSize < mnStringSize )
543 {
544 nByte = *pPtr;
545
546 if ( mpPara )
547 {
548 if ( ( nByte == ' ' ) || ( nByte == 0x09 ) )
549 {
550 if ( nCount == nNumb )
551 break;
552 else
553 mpPara = nullptr;
554 }
555 else
556 mnParaSize++;
557 }
558 else
559 {
560 if ( ( nByte != ' ' ) && ( nByte != 0x09 ) )
561 {
562 mpPara = pPtr;
563 mnParaSize = 1;
564 nCount++;
565 }
566 }
567 nSize++;
568 pPtr++;
569 }
570 return ( ( nCount == nNumb ) && mpPara );
571}
572
573// The next string is read and stored in mpStringBuf (terminated with 0);
574// mnStringSize contains the size of the string read.
575// Comments like '//' and '/*...*/' are skipped.
576
577bool XPMReader::ImplGetString()
578{
579 sal_uInt8 const sID[] = "/* XPM */";
580 sal_uInt8* pString = mpStringBuf;
581
582 mnStringSize = 0;
583 mpStringBuf[0] = 0;
584
585 while( mbStatus && ( mnStatus != XPMFINISHED ) )
586 {
587 if ( mnTempAvail == 0 )
588 {
589 mnTempAvail = mrIStm.ReadBytes( mpTempBuf, XPMTEMPBUFSIZE );
590 if ( mnTempAvail == 0 )
591 break;
592
593 mpTempPtr = mpTempBuf;
594
595 if ( mnIdentifier == XPMIDENTIFIER )
596 {
597 if ( mnTempAvail <= 50 )
598 {
599 mbStatus = false; // file is too short to be a correct XPM format
600 break;
601 }
602 for ( int i = 0; i < 9; i++ ) // searching for "/* XPM */"
603 if ( *mpTempPtr++ != sID[i] )
604 {
605 mbStatus = false;
606 break;
607 }
608 mnTempAvail-=9;
609 mnIdentifier++;
610 }
611 }
612 mcLastByte = mcThisByte;
613 mcThisByte = *mpTempPtr++;
614 mnTempAvail--;
615
616 if ( mnStatus & XPMDOUBLE )
617 {
618 if ( mcThisByte == 0x0a )
619 mnStatus &=~XPMDOUBLE;
620 continue;
621 }
622 if ( mnStatus & XPMREMARK )
623 {
624 if ( ( mcThisByte == '/' ) && ( mcLastByte == '*' ) )
625 mnStatus &=~XPMREMARK;
626 continue;
627 }
628 if ( mnStatus & XPMSTRING ) // characters in string
629 {
630 if ( mcThisByte == '"' )
631 {
632 mnStatus &=~XPMSTRING; // end of parameter by eol
633 break;
634 }
635 if ( mnStringSize >= ( XPMSTRINGBUF - 1 ) )
636 {
637 mbStatus = false;
638 break;
639 }
640 *pString++ = mcThisByte;
641 pString[0] = 0;
642 mnStringSize++;
643 continue;
644 }
645 else
646 { // characters beside string
647 switch ( mcThisByte )
648 {
649 case '*' :
650 if ( mcLastByte == '/' ) mnStatus |= XPMREMARK;
651 break;
652 case '/' :
653 if ( mcLastByte == '/' ) mnStatus |= XPMDOUBLE;
654 break;
655 case '"' : mnStatus |= XPMSTRING;
656 break;
657 case '{' :
658 if ( mnIdentifier == XPMDEFINITION )
659 mnIdentifier++;
660 break;
661 case '}' :
662 if ( mnIdentifier == XPMENDEXT )
663 mnStatus = XPMFINISHED;
664 break;
665 }
666 }
667 }
668 return mbStatus;
669}
670
671
672VCL_DLLPUBLIC bool ImportXPM( SvStream& rStm, Graphic& rGraphic )
673{
674 std::shared_ptr<GraphicReader> pContext = rGraphic.GetReaderContext();
675 rGraphic.SetReaderContext(nullptr);
676 XPMReader* pXPMReader = dynamic_cast<XPMReader*>( pContext.get() );
677 if (!pXPMReader)
678 {
679 pContext = std::make_shared<XPMReader>( rStm );
680 pXPMReader = static_cast<XPMReader*>( pContext.get() );
681 }
682
683 bool bRet = true;
684
685 ReadState eReadState = pXPMReader->ReadXPM( rGraphic );
686
687 if( eReadState == XPMREAD_ERROR )
688 {
689 bRet = false;
690 }
691 else if( eReadState == XPMREAD_NEED_MORE )
692 rGraphic.SetReaderContext( pContext );
693
694 return bRet;
695}
696
697/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
vcl::ScopedBitmapAccess< BitmapWriteAccess, Bitmap, &Bitmap::AcquireWriteAccess > BitmapScopedWriteAccess
ReadState
Definition: JpegReader.hxx:32
sal_uInt8 * Scanline
Definition: Scanline.hxx:26
void SetReaderContext(const std::shared_ptr< GraphicReader > &pReader)
Definition: graph.cxx:483
std::shared_ptr< GraphicReader > & GetReaderContext()
Definition: graph.cxx:478
This template handles BitmapAccess the RAII way.
int nCount
#define VCL_DLLPUBLIC
Definition: dllapi.h:29
#define ERRCODE_IO_PENDING
Definition: errcode.hxx:225
int i
long Long
const XPMRGBTab pRGBTable[]
Definition: rgbtable.hxx:33
double mnWidth
double mnHeight
sal_uIntPtr sal_uLong
sal_uInt8 blue
Definition: rgbtable.hxx:30
sal_uInt8 red
Definition: rgbtable.hxx:28
sal_uInt8 green
Definition: rgbtable.hxx:29
unsigned char sal_uInt8
#define XPMSTRINGBUF
Definition: xpmread.cxx:35
#define XPMENDEXT
Definition: xpmread.cxx:43
#define XPMPIXELS
Definition: xpmread.cxx:41
#define XPMDOUBLE
Definition: xpmread.cxx:46
#define XPMCOLORS
Definition: xpmread.cxx:40
#define XPMTEMPBUFSIZE
Definition: xpmread.cxx:34
#define XPMFINISHED
Definition: xpmread.cxx:48
#define XPMREMARK
Definition: xpmread.cxx:45
#define XPMDEFINITION
Definition: xpmread.cxx:38
#define XPMIDENTIFIER
Definition: xpmread.cxx:37
VCL_DLLPUBLIC bool ImportXPM(SvStream &rStm, Graphic &rGraphic)
Definition: xpmread.cxx:672
#define XPMEXTENSIONS
Definition: xpmread.cxx:42
#define XPMVALUES
Definition: xpmread.cxx:39
#define XPMSTRING
Definition: xpmread.cxx:47