1 /******************************************************************************
2 *
3 * Purpose: Block directory API.
4 *
5 ******************************************************************************
6 * Copyright (c) 2011
7 * PCI Geomatics, 90 Allstate Parkway, Markham, Ontario, Canada.
8 *
9 * Permission is hereby granted, free of charge, to any person obtaining a
10 * copy of this software and associated documentation files (the "Software"),
11 * to deal in the Software without restriction, including without limitation
12 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
13 * and/or sell copies of the Software, and to permit persons to whom the
14 * Software is furnished to do so, subject to the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included
17 * in all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
22 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 * DEALINGS IN THE SOFTWARE.
26 ****************************************************************************/
27
28 #include "blockdir/blocktilelayer.h"
29 #include "blockdir/blockdir.h"
30 #include "blockdir/blockfile.h"
31 #include "blockdir/asciitiledir.h"
32 #include "blockdir/binarytiledir.h"
33 #include "core/mutexholder.h"
34 #include "pcidsk_types.h"
35 #include "pcidsk_exception.h"
36 #include <cstdlib>
37 #include <cstring>
38 #include <cassert>
39 #include <algorithm>
40 #include <limits>
41
42 #ifdef PCIMAJORVERSION
43 #include "raster/memcmp.hh"
44 #include "raster/memset.hh"
45 #endif
46
47 namespace PCIDSK
48 {
49
50 /************************************************************************/
51 /* BlockTileLayer() */
52 /************************************************************************/
53
54 /**
55 * Constructor.
56 *
57 * @param poBlockDir The associated block directory.
58 * @param nLayer The index of the block layer.
59 * @param psBlockLayer The block layer info.
60 * @param psTileLayer The tile layer info.
61 */
BlockTileLayer(BlockDir * poBlockDir,uint32 nLayer,BlockLayerInfo * psBlockLayer,TileLayerInfo * psTileLayer)62 BlockTileLayer::BlockTileLayer(BlockDir * poBlockDir, uint32 nLayer,
63 BlockLayerInfo * psBlockLayer,
64 TileLayerInfo * psTileLayer)
65 : BlockLayer(poBlockDir, nLayer),
66 mpsBlockLayer(psBlockLayer),
67 mpsTileLayer(psTileLayer),
68 mbModified(false)
69 {
70 memset(mszDataType, 0, sizeof(mszDataType));
71 memset(mszCompress, 0, sizeof(mszCompress));
72
73 mpoTileListMutex = DefaultCreateMutex();
74 }
75
76 /************************************************************************/
77 /* ~BlockTileLayer() */
78 /************************************************************************/
79
80 /**
81 * Destructor.
82 */
~BlockTileLayer(void)83 BlockTileLayer::~BlockTileLayer(void)
84 {
85 delete mpoTileListMutex;
86 }
87
88 /************************************************************************/
89 /* GetTileInfo() */
90 /************************************************************************/
91
92 /**
93 * Gets the tile at the specified index.
94 *
95 * @param nCol The column of the tile.
96 * @param nRow The row of the tile.
97 *
98 * @return The tile at the specified index.
99 */
100 BlockTileLayer::BlockTileInfo *
GetTileInfo(uint32 nCol,uint32 nRow)101 BlockTileLayer::GetTileInfo(uint32 nCol, uint32 nRow)
102 {
103 if (!IsValid())
104 return nullptr;
105
106 uint32 nTilesPerRow = GetTilePerRow();
107
108 uint32 iTile = nRow * nTilesPerRow + nCol;
109
110 MutexHolder oLock(mpoTileListMutex);
111
112 if (moTileList.empty())
113 ReadTileList();
114
115 return &moTileList.at(iTile);
116 }
117
118 /************************************************************************/
119 /* Sync() */
120 /************************************************************************/
121
122 /**
123 * Synchronizes the block tile layer to disk.
124 */
Sync(void)125 void BlockTileLayer::Sync(void)
126 {
127 if (!mbModified)
128 return;
129
130 try
131 {
132 if (!GetFile()->GetUpdatable())
133 return;
134 }
135 catch (...)
136 {
137 return;
138 }
139
140 MutexHolder oLock(mpoTileListMutex);
141
142 if (!mbModified)
143 return;
144
145 WriteTileList();
146
147 mbModified = false;
148 }
149
150 /************************************************************************/
151 /* IsCorrupted() */
152 /************************************************************************/
IsCorrupted(void) const153 bool BlockTileLayer::IsCorrupted(void) const
154 {
155 // Dead layers have a tile size of 0, but it should be considered valid.
156 if (GetLayerType() == BLTDead)
157 return false;
158
159 // The tile layer is corrupted when the image size is 0.
160 if (GetXSize() == 0 || GetYSize() == 0)
161 return true;
162
163 uint64 nTileSize =
164 static_cast<uint64>(GetTileXSize()) * GetTileYSize() * GetDataTypeSize();
165
166 return nTileSize == 0 || nTileSize > std::numeric_limits<uint32>::max();
167 }
168
169 /************************************************************************/
170 /* GetTileCount() */
171 /************************************************************************/
172
173 /**
174 * Gets the number of tiles in the tile layer.
175 *
176 * @return The number of tiles in the tile layer.
177 */
GetTileCount(void) const178 uint32 BlockTileLayer::GetTileCount(void) const
179 {
180 return (uint32) (((static_cast<uint64>(GetXSize()) + GetTileXSize() - 1) / GetTileXSize()) *
181 ((static_cast<uint64>(GetYSize()) + GetTileYSize() - 1) / GetTileYSize()));
182 }
183
184 /************************************************************************/
185 /* GetTilePerRow() */
186 /************************************************************************/
187
188 /**
189 * Gets the number of tiles per row in the tile layer.
190 *
191 * @return The number of tiles per row in the tile layer.
192 */
GetTilePerRow(void) const193 uint32 BlockTileLayer::GetTilePerRow(void) const
194 {
195 return (uint32) (static_cast<uint64>(GetXSize()) + GetTileXSize() - 1) / GetTileXSize();
196 }
197
198 /************************************************************************/
199 /* GetTilePerCol() */
200 /************************************************************************/
201
202 /**
203 * Gets the number of tiles per column in the tile layer.
204 *
205 * @return The number of tiles per column in the tile layer.
206 */
GetTilePerCol(void) const207 uint32 BlockTileLayer::GetTilePerCol(void) const
208 {
209 return (uint32) (static_cast<uint64>(GetYSize()) + GetTileYSize() - 1) / GetTileYSize();
210 }
211
212 /************************************************************************/
213 /* GetTileSize() */
214 /************************************************************************/
215
216 /**
217 * Gets the size in bytes of a tile.
218 *
219 * @return The size in bytes of a tile.
220 */
GetTileSize(void) const221 uint32 BlockTileLayer::GetTileSize(void) const
222 {
223 return GetTileXSize() * GetTileYSize() * GetDataTypeSize();
224 }
225
226 /************************************************************************/
227 /* GetDataTypeSize() */
228 /************************************************************************/
229
230 /**
231 * Gets the data type size in bytes.
232 *
233 * @return The data type size in bytes.
234 */
GetDataTypeSize(void) const235 uint32 BlockTileLayer::GetDataTypeSize(void) const
236 {
237 return DataTypeSize(GetDataTypeFromName(GetDataType()));
238 }
239
240 /************************************************************************/
241 /* IsTileValid() */
242 /************************************************************************/
243
244 /**
245 * Checks if the specified tile is valid.
246 *
247 * @param nCol The column of the tile.
248 * @param nRow The row of the tile.
249 *
250 * @return If the specified tile is valid.
251 */
IsTileValid(uint32 nCol,uint32 nRow)252 bool BlockTileLayer::IsTileValid(uint32 nCol, uint32 nRow)
253 {
254 BlockTileInfo * psTile = GetTileInfo(nCol, nRow);
255
256 return (psTile && psTile->nOffset != INVALID_OFFSET && psTile->nSize != 0 &&
257 AreBlocksAllocated(psTile->nOffset, psTile->nSize));
258 }
259
260 /************************************************************************/
261 /* GetTileDataSize() */
262 /************************************************************************/
263
264 /**
265 * Gets the size in bytes of the specified tile.
266 *
267 * @param nCol The column of the tile.
268 * @param nRow The row of the tile.
269 *
270 * @return The size in bytes of the specified tile.
271 */
GetTileDataSize(uint32 nCol,uint32 nRow)272 uint32 BlockTileLayer::GetTileDataSize(uint32 nCol, uint32 nRow)
273 {
274 BlockTileInfo * psTile = GetTileInfo(nCol, nRow);
275
276 return psTile ? psTile->nSize : 0;
277 }
278
279 /************************************************************************/
280 /* WriteSparseTile() */
281 /************************************************************************/
282
283 /**
284 * Writes the specified tile only if the data is sparse.
285 *
286 * @param pData The data of the tile.
287 * @param nCol The column of the tile.
288 * @param nRow The row of the tile.
289 *
290 * @return If the specified tile data is sparse.
291 */
WriteSparseTile(const void * pData,uint32 nCol,uint32 nRow)292 bool BlockTileLayer::WriteSparseTile(const void * pData,
293 uint32 nCol, uint32 nRow)
294 {
295 MutexHolder oLock(mpoTileListMutex);
296
297 uint32 nValue = 0;
298
299 bool bIsSparse = true;
300
301 uint32 nTileSize = GetTileSize();
302
303 // Check if we can use a sparse tile with a 4 byte value.
304 if (dynamic_cast<BinaryTileDir *>(mpoBlockDir) && nTileSize % 4 == 0)
305 {
306 uint32 * pnIter = (uint32 *) pData;
307
308 nValue = *pnIter;
309
310 #ifdef PCIMAJORVERSION
311 bIsSparse = raster::memcmp32(pnIter, nValue,
312 nTileSize / sizeof(uint32));
313 #else
314 uint32 * pnEnd = pnIter + nTileSize / sizeof(uint32);
315 for (; pnIter < pnEnd; ++pnIter)
316 {
317 if (*pnIter != nValue)
318 {
319 bIsSparse = false;
320 break;
321 }
322 }
323 #endif
324 }
325 // Check if we can use a sparse tile with a value of 0.
326 else
327 {
328 nValue = 0;
329
330 #ifdef PCIMAJORVERSION
331 bIsSparse = raster::memcmp8((uchar *) pData, 0, nTileSize);
332 #else
333 uchar * pnIter = (uchar *) pData;
334 uchar * pnEnd = pnIter + nTileSize;
335 for (; pnIter < pnEnd; ++pnIter)
336 {
337 if (*pnIter != nValue)
338 {
339 bIsSparse = false;
340 break;
341 }
342 }
343 #endif
344 }
345
346 // If the tile data is sparse store the sparse value in the nSize member
347 // of the BlockTileInfo structure.
348 if (bIsSparse)
349 {
350 BlockTileInfo * psTile = GetTileInfo(nCol, nRow);
351 if( psTile != nullptr ) // TODO: what if it is null
352 {
353 // Free the blocks used by the tile.
354 if (psTile->nOffset != INVALID_OFFSET)
355 FreeBlocks(psTile->nOffset, psTile->nSize);
356
357 psTile->nOffset = INVALID_OFFSET;
358
359 psTile->nSize = nValue;
360
361 mbModified = true;
362 }
363 }
364
365 return bIsSparse;
366 }
367
368 /************************************************************************/
369 /* WriteTile() */
370 /************************************************************************/
371
372 /**
373 * Writes the specified tile.
374 *
375 * @param pData The data of the tile.
376 * @param nCol The column of the tile.
377 * @param nRow The row of the tile.
378 * @param nSize The size of the tile.
379 */
WriteTile(const void * pData,uint32 nCol,uint32 nRow,uint32 nSize)380 void BlockTileLayer::WriteTile(const void * pData,
381 uint32 nCol, uint32 nRow, uint32 nSize)
382 {
383 MutexHolder oLock(mpoTileListMutex);
384
385 if (!IsValid())
386 return;
387
388 BlockTileInfo * psTile = GetTileInfo(nCol, nRow);
389
390 if (!psTile)
391 return;
392
393 if (nSize == 0)
394 nSize = GetTileSize();
395
396 if (psTile->nOffset == INVALID_OFFSET)
397 {
398 psTile->nOffset = GetLayerSize();
399
400 psTile->nSize = nSize;
401
402 mbModified = true;
403 }
404
405 if (psTile->nSize < nSize)
406 {
407 psTile->nOffset = GetLayerSize();
408
409 psTile->nSize = nSize;
410
411 mbModified = true;
412 }
413 else if (psTile->nSize > nSize)
414 {
415 psTile->nSize = nSize;
416
417 mbModified = true;
418 }
419
420 WriteToLayer(pData, psTile->nOffset, psTile->nSize);
421 }
422
423 /************************************************************************/
424 /* ReadSparseTile() */
425 /************************************************************************/
426
427 /**
428 * Reads the specified tile only if the data is sparse.
429 *
430 * @param pData The data of the tile.
431 * @param nCol The column of the tile.
432 * @param nRow The row of the tile.
433 *
434 * @return If the specified tile data is sparse.
435 */
ReadSparseTile(void * pData,uint32 nCol,uint32 nRow)436 bool BlockTileLayer::ReadSparseTile(void * pData, uint32 nCol, uint32 nRow)
437 {
438 if (!IsValid())
439 return false;
440
441 BlockTileInfo * psTile = GetTileInfo(nCol, nRow);
442
443 if (!psTile)
444 return false;
445
446 if (psTile->nOffset != INVALID_OFFSET)
447 return false;
448
449 uint32 nTileSize = GetTileSize();
450
451 // Check if we can use a sparse tile with a 4 byte value.
452 if (dynamic_cast<BinaryTileDir *>(mpoBlockDir) && nTileSize % 4 == 0)
453 {
454 #ifdef PCIMAJORVERSION
455 raster::memset32((uint32 *) pData, psTile->nSize,
456 nTileSize / sizeof(uint32));
457 #else
458 uint32 * pnIter = (uint32 *) pData;
459 uint32 * pnEnd = pnIter + nTileSize / sizeof(uint32);
460 for (; pnIter < pnEnd; ++pnIter)
461 *pnIter = psTile->nSize;
462 #endif
463 }
464 // Check if we can use a sparse tile with a value of 0.
465 else
466 {
467 memset(pData, 0, nTileSize);
468 }
469
470 return true;
471 }
472
473 /************************************************************************/
474 /* ReadTile() */
475 /************************************************************************/
476
477 /**
478 * Reads the specified tile.
479 *
480 * @param pData The data of the tile.
481 * @param nCol The column of the tile.
482 * @param nRow The row of the tile.
483 * @param nSize The buffer size.
484 *
485 * @return The size of the tile.
486 */
ReadTile(void * pData,uint32 nCol,uint32 nRow,uint32 nSize)487 uint32 BlockTileLayer::ReadTile(void * pData, uint32 nCol, uint32 nRow, uint32 nSize)
488 {
489 if (!IsValid())
490 return 0;
491
492 BlockTileInfo * psTile = GetTileInfo(nCol, nRow);
493
494 if (!psTile)
495 return 0;
496
497 if (psTile->nOffset == INVALID_OFFSET)
498 return 0;
499
500 if (psTile->nSize == 0)
501 return 0;
502
503 uint32 nReadSize = std::min(nSize, psTile->nSize);
504
505 #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
506 assert(psTile->nSize == nSize);
507 #endif
508
509 if (!ReadFromLayer(pData, psTile->nOffset, nReadSize))
510 return 0;
511
512 return nReadSize;
513 }
514
515 /************************************************************************/
516 /* ReadPartialSparseTile() */
517 /************************************************************************/
518
519 /**
520 * Reads the specified tile only if the data is sparse.
521 *
522 * @param pData The data of the tile.
523 * @param nCol The column of the tile.
524 * @param nRow The row of the tile.
525 * @param nOffset The offset of the data.
526 * @param nSize The size of the data.
527 *
528 * @return If the specified tile data is sparse.
529 */
ReadPartialSparseTile(void * pData,uint32 nCol,uint32 nRow,uint32 nOffset,uint32 nSize)530 bool BlockTileLayer::ReadPartialSparseTile(void * pData,
531 uint32 nCol, uint32 nRow,
532 uint32 nOffset, uint32 nSize)
533 {
534 if (!IsValid())
535 return false;
536
537 BlockTileInfo * psTile = GetTileInfo(nCol, nRow);
538
539 if (!psTile)
540 return false;
541
542 if (psTile->nOffset != INVALID_OFFSET)
543 return false;
544
545 uint32 nTileSize = GetTileSize();
546
547 // Check if we can use a sparse tile with a 4 byte value.
548 if (dynamic_cast<BinaryTileDir *>(mpoBlockDir) && nTileSize % 4 == 0)
549 {
550 uint32 nValue = psTile->nSize;
551
552 // We need to bitwise shift the value if the offset isn't aligned.
553 uint32 nByteOffset = nOffset % 4;
554
555 if (nByteOffset != 0)
556 {
557 uint32 nBitOffset = nByteOffset * 8;
558
559 nValue = (nValue << nBitOffset) | (nValue >> (32 - nBitOffset));
560 }
561
562 uint32 nAlign = nSize / sizeof(uint32);
563
564 #ifdef PCIMAJORVERSION
565 raster::memset32((uint32 *) pData, nValue, nAlign);
566 #else
567 uint32 * pnIter = (uint32 *) pData;
568 uint32 * pnEnd = pnIter + nAlign;
569 for (; pnIter < pnEnd; ++pnIter)
570 *pnIter = nValue;
571 #endif
572
573 uint32 nRemaining = nSize % 4;
574
575 if (nRemaining != 0)
576 {
577 uchar * pbyIter = (uchar *) pData + nAlign * 4;
578
579 do
580 {
581 nValue = (nValue << 8) | (nValue >> 24);
582
583 *pbyIter++ = (uchar) nValue;
584 }
585 while (--nRemaining);
586 }
587 }
588 // Check if we can use a sparse tile with a value of 0.
589 else
590 {
591 memset(pData, 0, nSize);
592 }
593
594 return true;
595 }
596
597 /************************************************************************/
598 /* ReadPartialTile() */
599 /************************************************************************/
600
601 /**
602 * Reads the specified tile.
603 *
604 * @param pData The data of the tile.
605 * @param nCol The column of the tile.
606 * @param nRow The row of the tile.
607 * @param nOffset The offset of the data.
608 * @param nSize The size of the data.
609 *
610 * @return If the read was successful.
611 */
ReadPartialTile(void * pData,uint32 nCol,uint32 nRow,uint32 nOffset,uint32 nSize)612 bool BlockTileLayer::ReadPartialTile(void * pData, uint32 nCol, uint32 nRow,
613 uint32 nOffset, uint32 nSize)
614 {
615 if (!IsValid())
616 return false;
617
618 BlockTileInfo * psTile = GetTileInfo(nCol, nRow);
619
620 if (!psTile)
621 return false;
622
623 if (psTile->nOffset == INVALID_OFFSET)
624 return false;
625
626 if (psTile->nSize == 0 || psTile->nSize < nOffset + nSize)
627 return false;
628
629 if (!ReadFromLayer(pData, psTile->nOffset + nOffset, nSize))
630 return false;
631
632 return true;
633 }
634
635 /************************************************************************/
636 /* SetTileLayerInfo() */
637 /************************************************************************/
638
639 /**
640 * Sets the tile layer information.
641 *
642 * @param nXSize The width of the tile layer.
643 * @param nYSize The height of the tile layer.
644 * @param nTileXSize The width of a tile.
645 * @param nTileYSize The height of a tile.
646 * @param oDataType The data type of the tile layer.
647 * @param oCompress The compress type of the tile layer.
648 * @param bNoDataValid If the NoData value is valid.
649 * @param dfNoDataValue The NoData value of the tile layer.
650 */
SetTileLayerInfo(uint32 nXSize,uint32 nYSize,uint32 nTileXSize,uint32 nTileYSize,const std::string & oDataType,const std::string & oCompress,bool bNoDataValid,double dfNoDataValue)651 void BlockTileLayer::SetTileLayerInfo(uint32 nXSize, uint32 nYSize,
652 uint32 nTileXSize, uint32 nTileYSize,
653 const std::string & oDataType,
654 const std::string & oCompress,
655 bool bNoDataValid, double dfNoDataValue)
656 {
657 uint64 nTileSize =
658 static_cast<uint64>(nTileXSize) * nTileYSize *
659 DataTypeSize(GetDataTypeFromName(oDataType.c_str()));
660
661 if (nTileSize == 0 || nTileSize > std::numeric_limits<uint32>::max())
662 {
663 return ThrowPCIDSKException("Invalid tile dimensions: %d x %d",
664 nTileXSize, nTileYSize);
665 }
666
667 if (nXSize == 0 || nYSize == 0)
668 {
669 return ThrowPCIDSKException("Invalid tile layer dimensions: %d x %d",
670 nXSize, nYSize);
671 }
672
673 mpsTileLayer->nXSize = nXSize;
674 mpsTileLayer->nYSize = nYSize;
675 mpsTileLayer->nTileXSize = nTileXSize;
676 mpsTileLayer->nTileYSize = nTileYSize;
677 mpsTileLayer->bNoDataValid = bNoDataValid;
678 mpsTileLayer->dfNoDataValue = dfNoDataValue;
679
680 memset(mpsTileLayer->szDataType, ' ', 4);
681 memcpy(mpsTileLayer->szDataType, oDataType.data(), oDataType.size());
682
683 memset(mpsTileLayer->szCompress, ' ', 8);
684 memcpy(mpsTileLayer->szCompress, oCompress.data(), oCompress.size());
685
686 // Invalidate the cache variables.
687 *mszDataType = 0;
688 *mszCompress = 0;
689
690 // Initialize the tile list.
691 uint32 nTileCount = GetTileCount();
692
693 MutexHolder oLock(mpoTileListMutex);
694
695 try
696 {
697 moTileList.resize(nTileCount);
698 }
699 catch (const std::exception & ex)
700 {
701 return ThrowPCIDSKException("Out of memory in BlockTileLayer::SetTileLayerInfo(): %s", ex.what());
702 }
703
704 for (uint32 iTile = 0; iTile < nTileCount; iTile++)
705 {
706 BlockTileInfo * psTile = &moTileList[iTile];
707
708 psTile->nOffset = INVALID_OFFSET;
709 psTile->nSize = 0;
710 }
711
712 // Write the tile list to disk.
713 WriteTileList();
714
715 mbModified = false;
716
717 oLock.Release();
718
719 // Make sure that the first tile starts on a block boundary.
720 uint64 nLayerSize = GetLayerSize();
721 uint32 nBlockSize = mpoBlockDir->GetBlockSize();
722
723 if (nLayerSize % nBlockSize != 0)
724 Resize((nLayerSize / nBlockSize + 1) * nBlockSize);
725 }
726
727 /************************************************************************/
728 /* GetDataType() */
729 /************************************************************************/
730
731 /**
732 * Gets the data type of the tile layer.
733 *
734 * @return The data type of the tile layer.
735 */
GetDataType(void) const736 const char * BlockTileLayer::GetDataType(void) const
737 {
738 if (*mszDataType)
739 return mszDataType;
740
741 MutexHolder oLock(mpoTileListMutex);
742
743 if (*mszDataType)
744 return mszDataType;
745
746 memcpy(mszDataType, mpsTileLayer->szDataType, 4);
747
748 int nIter = 3;
749
750 while (nIter > 0 && mszDataType[nIter] == ' ')
751 mszDataType[nIter--] = '\0';
752
753 return mszDataType;
754 }
755
756 /************************************************************************/
757 /* GetCompressType() */
758 /************************************************************************/
759
760 /**
761 * Gets the compress type of the tile layer.
762 *
763 * @return The compress type of the tile layer.
764 */
GetCompressType(void) const765 const char * BlockTileLayer::GetCompressType(void) const
766 {
767 if (*mszCompress)
768 return mszCompress;
769
770 MutexHolder oLock(mpoTileListMutex);
771
772 if (*mszCompress)
773 return mszCompress;
774
775 memcpy(mszCompress, mpsTileLayer->szCompress, 8);
776
777 int nIter = 7;
778
779 while (nIter > 0 && mszCompress[nIter] == ' ')
780 mszCompress[nIter--] = '\0';
781
782 return mszCompress;
783 }
784
785 } // namespace PCIDSK
786