1 : /******************************************************************************
2 : * $Id: bsb_read.c 20996 2010-10-28 18:38:15Z rouault $
3 : *
4 : * Project: BSB Reader
5 : * Purpose: Low level BSB Access API Implementation (non-GDAL).
6 : * Author: Frank Warmerdam, warmerdam@pobox.com
7 : *
8 : * NOTE: This code is implemented on the basis of work by Mike Higgins. The
9 : * BSB format is subject to US patent 5,727,090; however, that patent
10 : * apparently only covers *writing* BSB files, not reading them, so this code
11 : * should not be affected.
12 : *
13 : ******************************************************************************
14 : * Copyright (c) 2001, Frank Warmerdam
15 : *
16 : * Permission is hereby granted, free of charge, to any person obtaining a
17 : * copy of this software and associated documentation files (the "Software"),
18 : * to deal in the Software without restriction, including without limitation
19 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
20 : * and/or sell copies of the Software, and to permit persons to whom the
21 : * Software is furnished to do so, subject to the following conditions:
22 : *
23 : * The above copyright notice and this permission notice shall be included
24 : * in all copies or substantial portions of the Software.
25 : *
26 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
27 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
29 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
31 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
32 : * DEALINGS IN THE SOFTWARE.
33 : ****************************************************************************/
34 :
35 : #include "bsb_read.h"
36 : #include "cpl_conv.h"
37 : #include "cpl_string.h"
38 :
39 : CPL_CVSID("$Id: bsb_read.c 20996 2010-10-28 18:38:15Z rouault $");
40 :
41 : static int BSBReadHeaderLine( BSBInfo *psInfo, char* pszLine, int nLineMaxLen, int bNO1 );
42 : static int BSBSeekAndCheckScanlineNumber ( BSBInfo *psInfo, int nScanline,
43 : int bVerboseIfError );
44 : /************************************************************************
45 :
46 : Background:
47 :
48 : To: Frank Warmerdam <warmerda@home.com>
49 : From: Mike Higgins <higgins@monitor.net>
50 : Subject: Re: GISTrans: Maptech / NDI BSB Chart Format
51 : Mime-Version: 1.0
52 : Content-Type: text/plain; charset="us-ascii"; format=flowed
53 :
54 : I did it! I just wrote a program that reads NOAA BSB chart files
55 : and converts them to BMP files! BMP files are not the final goal of my
56 : project, but it served as a proof-of-concept. Next I will want to write
57 : routines to extract pieces of the file at full resolution for printing, and
58 : routines to filter pieces of the chart for display at lower resolution on
59 : the screen. (One of the terrible things about most chart display programs
60 : is that they all sub-sample the charts instead of filtering it down). How
61 : did I figure out how to read the BSB files?
62 :
63 : If you recall, I have been trying to reverse engineer the file
64 : formats of those nautical charts. When I am between projects I often do a
65 : WEB search for the BSB file format to see if someone else has published a
66 : hack for them. Monday I hit a NOAA project status report that mentioned
67 : some guy named Marty Yellin who had recently completed writing a program to
68 : convert BSB files to other file formats! I searched for him and found him
69 : mentioned as a contact person for some NOAA program. I was composing a
70 : letter to him in my head, or considering calling the NOAA phone number and
71 : asking for his extension number, when I saw another NOAA status report
72 : indicating that he had retired in 1998. His name showed up in a few more
73 : reports, one of which said that he was the inventor of the BSB file format,
74 : that it was patented (#5,727,090), and that the patent had been licensed to
75 : Maptech (the evil company that will not allow anyone using their file
76 : format to convert them to non-proprietary formats). Patents are readily
77 : available on the WEB at the IBM patent server and this one is in the
78 : dtabase! I printed up a copy of the patent and of course it describes very
79 : nicely (despite the usual typos and omissions of referenced items in the
80 : figures) how to write one of these BSB files!
81 :
82 : I was considering talking to a patent lawyer about the legality of
83 : using information in the patent to read files without getting a license,
84 : when I noticed that the patent is only claiming programs that WRITE the
85 : file format. I have noticed this before in RF patents where they describe
86 : how to make a receiver and never bother to claim a transmitter. The logic
87 : is that the transmitter is no good to anybody unless they license receivers
88 : from the patent holder. But I think they did it backwards here! They should
89 : have claimed a program that can READ the described file format. Now I can
90 : read the files, build programs that read the files, and even sell them
91 : without violating the claims in the patent! As long as I never try to write
92 : one of the evil BSB files, I'm OK!!!
93 :
94 : If you ever need to read these BSB chart programs, drop me a
95 : note. I would be happy to send you a copy of this conversion program.
96 :
97 : ... later email ...
98 :
99 : Well, here is my little proof of concept program. I hereby give
100 : you permission to distribute it freely, modify for you own use, etc.
101 : I built it as a "WIN32 Console application" which means it runs in an MS
102 : DOS box under Microsoft Windows. But the only Windows specific stuff in it
103 : are the include files for the BMP file headers. If you ripped out the BMP
104 : code it should compile under UNIX or anyplace else.
105 : I'd be overjoyed to have you announce it to GISTrans or anywhere
106 : else. I'm philosophically opposed to the proprietary treatment of the BSB
107 : file format and I want to break it open! Chart data for the People!
108 :
109 : ************************************************************************/
110 :
111 : /************************************************************************/
112 : /* BSBUngetc() */
113 : /************************************************************************/
114 :
115 : static
116 1335 : void BSBUngetc( BSBInfo *psInfo, int nCharacter )
117 :
118 : {
119 1335 : CPLAssert( psInfo->nSavedCharacter == -1000 );
120 1335 : psInfo->nSavedCharacter = nCharacter;
121 1335 : }
122 :
123 : /************************************************************************/
124 : /* BSBGetc() */
125 : /************************************************************************/
126 :
127 : static
128 26244 : int BSBGetc( BSBInfo *psInfo, int bNO1, int* pbErrorFlag )
129 :
130 : {
131 : int nByte;
132 :
133 26244 : if( psInfo->nSavedCharacter != -1000 )
134 : {
135 1335 : nByte = psInfo->nSavedCharacter;
136 1335 : psInfo->nSavedCharacter = -1000;
137 1335 : return nByte;
138 : }
139 :
140 24909 : if( psInfo->nBufferOffset >= psInfo->nBufferSize )
141 : {
142 318 : psInfo->nBufferOffset = 0;
143 318 : psInfo->nBufferSize =
144 318 : VSIFReadL( psInfo->pabyBuffer, 1, psInfo->nBufferAllocation,
145 : psInfo->fp );
146 318 : if( psInfo->nBufferSize <= 0 )
147 : {
148 2 : if (pbErrorFlag)
149 2 : *pbErrorFlag = TRUE;
150 2 : return 0;
151 : }
152 : }
153 :
154 24907 : nByte = psInfo->pabyBuffer[psInfo->nBufferOffset++];
155 :
156 24907 : if( bNO1 )
157 : {
158 0 : nByte = nByte - 9;
159 0 : if( nByte < 0 )
160 0 : nByte = nByte + 256;
161 : }
162 :
163 24907 : return nByte;
164 : }
165 :
166 :
167 : /************************************************************************/
168 : /* BSBOpen() */
169 : /* */
170 : /* Read BSB header, and return information. */
171 : /************************************************************************/
172 :
173 5 : BSBInfo *BSBOpen( const char *pszFilename )
174 :
175 : {
176 : VSILFILE *fp;
177 : char achTestBlock[1000];
178 : char szLine[1000];
179 5 : int i, bNO1 = FALSE;
180 : BSBInfo *psInfo;
181 5 : int nSkipped = 0;
182 : const char *pszPalette;
183 : int nOffsetFirstLine;
184 5 : int bErrorFlag = FALSE;
185 :
186 : /* -------------------------------------------------------------------- */
187 : /* Which palette do we want to use? */
188 : /* -------------------------------------------------------------------- */
189 5 : pszPalette = CPLGetConfigOption( "BSB_PALETTE", "RGB" );
190 :
191 : /* -------------------------------------------------------------------- */
192 : /* Open the file. */
193 : /* -------------------------------------------------------------------- */
194 5 : fp = VSIFOpenL( pszFilename, "rb" );
195 5 : if( fp == NULL )
196 : {
197 0 : CPLError( CE_Failure, CPLE_OpenFailed,
198 : "File %s not found.", pszFilename );
199 0 : return NULL;
200 : }
201 :
202 : /* -------------------------------------------------------------------- */
203 : /* Read the first 1000 bytes, and verify that it contains the */
204 : /* "BSB/" keyword" */
205 : /* -------------------------------------------------------------------- */
206 5 : if( VSIFReadL( achTestBlock, 1, sizeof(achTestBlock), fp )
207 : != sizeof(achTestBlock) )
208 : {
209 0 : VSIFCloseL( fp );
210 0 : CPLError( CE_Failure, CPLE_FileIO,
211 : "Could not read first %d bytes for header!",
212 : (int) sizeof(achTestBlock) );
213 0 : return NULL;
214 : }
215 :
216 140 : for( i = 0; i < sizeof(achTestBlock) - 4; i++ )
217 : {
218 : /* Test for "BSB/" */
219 150 : if( achTestBlock[i+0] == 'B' && achTestBlock[i+1] == 'S'
220 10 : && achTestBlock[i+2] == 'B' && achTestBlock[i+3] == '/' )
221 5 : break;
222 :
223 : /* Test for "NOS/" */
224 135 : if( achTestBlock[i+0] == 'N' && achTestBlock[i+1] == 'O'
225 0 : && achTestBlock[i+2] == 'S' && achTestBlock[i+3] == '/' )
226 0 : break;
227 :
228 : /* Test for "NOS/" offset by 9 in ASCII for NO1 files */
229 135 : if( achTestBlock[i+0] == 'W' && achTestBlock[i+1] == 'X'
230 0 : && achTestBlock[i+2] == '\\' && achTestBlock[i+3] == '8' )
231 : {
232 0 : bNO1 = TRUE;
233 0 : break;
234 : }
235 : }
236 :
237 5 : if( i == sizeof(achTestBlock) - 4 )
238 : {
239 0 : VSIFCloseL( fp );
240 0 : CPLError( CE_Failure, CPLE_AppDefined,
241 : "This does not appear to be a BSB file, no BSB/ header." );
242 0 : return NULL;
243 : }
244 :
245 : /* -------------------------------------------------------------------- */
246 : /* Create info structure. */
247 : /* -------------------------------------------------------------------- */
248 5 : psInfo = (BSBInfo *) CPLCalloc(1,sizeof(BSBInfo));
249 5 : psInfo->fp = fp;
250 5 : psInfo->bNO1 = bNO1;
251 :
252 5 : psInfo->nBufferAllocation = 1024;
253 5 : psInfo->pabyBuffer = (GByte *) CPLMalloc(psInfo->nBufferAllocation);
254 5 : psInfo->nBufferSize = 0;
255 5 : psInfo->nBufferOffset = 0;
256 5 : psInfo->nSavedCharacter = -1000;
257 :
258 : /* -------------------------------------------------------------------- */
259 : /* Rewind, and read line by line. */
260 : /* -------------------------------------------------------------------- */
261 5 : VSIFSeekL( fp, 0, SEEK_SET );
262 :
263 665 : while( BSBReadHeaderLine(psInfo, szLine, sizeof(szLine), bNO1) )
264 : {
265 655 : char **papszTokens = NULL;
266 655 : int nCount = 0;
267 :
268 1305 : if( szLine[0] != '\0' && szLine[1] != '\0' && szLine[2] != '\0' && szLine[3] == '/' )
269 : {
270 650 : psInfo->papszHeader = CSLAddString( psInfo->papszHeader, szLine );
271 650 : papszTokens = CSLTokenizeStringComplex( szLine+4, ",=",
272 : FALSE,FALSE);
273 650 : nCount = CSLCount(papszTokens);
274 : }
275 5 : else if( EQUALN(szLine," ",4) && szLine[4] != ' ' )
276 : {
277 : /* add extension lines to the last header line. */
278 0 : int iTargetHeader = CSLCount(psInfo->papszHeader);
279 :
280 0 : if( iTargetHeader != -1 )
281 : {
282 0 : psInfo->papszHeader[iTargetHeader] = (char *)
283 0 : CPLRealloc(psInfo->papszHeader[iTargetHeader],
284 0 : strlen(psInfo->papszHeader[iTargetHeader])
285 0 : + strlen(szLine) + 5 );
286 0 : strcat( psInfo->papszHeader[iTargetHeader], "," );
287 0 : strcat( psInfo->papszHeader[iTargetHeader], szLine+4 );
288 : }
289 : }
290 :
291 655 : if( EQUALN(szLine,"BSB/",4) )
292 : {
293 : int nRAIndex;
294 :
295 5 : nRAIndex = CSLFindString(papszTokens, "RA" );
296 5 : if( nRAIndex < 0 || nRAIndex+2 >= nCount )
297 : {
298 0 : CSLDestroy( papszTokens );
299 0 : CPLError( CE_Failure, CPLE_AppDefined,
300 : "Failed to extract RA from BSB/ line." );
301 0 : BSBClose( psInfo );
302 0 : return NULL;
303 : }
304 5 : psInfo->nXSize = atoi(papszTokens[nRAIndex+1]);
305 5 : psInfo->nYSize = atoi(papszTokens[nRAIndex+2]);
306 : }
307 650 : else if( EQUALN(szLine,"NOS/",4) )
308 : {
309 : int nRAIndex;
310 :
311 0 : nRAIndex = CSLFindString(papszTokens, "RA" );
312 0 : if( nRAIndex < 0 || nRAIndex+4 >= nCount )
313 : {
314 0 : CSLDestroy( papszTokens );
315 0 : CPLError( CE_Failure, CPLE_AppDefined,
316 : "Failed to extract RA from NOS/ line." );
317 0 : BSBClose( psInfo );
318 0 : return NULL;
319 : }
320 0 : psInfo->nXSize = atoi(papszTokens[nRAIndex+3]);
321 0 : psInfo->nYSize = atoi(papszTokens[nRAIndex+4]);
322 : }
323 1285 : else if( EQUALN(szLine, pszPalette, 3) && szLine[3] == '/'
324 : && nCount >= 4 )
325 : {
326 635 : int iPCT = atoi(papszTokens[0]);
327 635 : if (iPCT < 0 || iPCT > 128)
328 : {
329 0 : CSLDestroy( papszTokens );
330 0 : CPLError( CE_Failure, CPLE_OutOfMemory,
331 : "BSBOpen : Invalid color table index. Probably due to corrupted BSB file (iPCT = %d).",
332 : iPCT);
333 0 : BSBClose( psInfo );
334 0 : return NULL;
335 : }
336 635 : if( iPCT > psInfo->nPCTSize-1 )
337 : {
338 1270 : unsigned char* pabyNewPCT = (unsigned char *)
339 1905 : VSIRealloc(psInfo->pabyPCT,(iPCT+1) * 3);
340 635 : if (pabyNewPCT == NULL)
341 : {
342 0 : CSLDestroy( papszTokens );
343 0 : CPLError( CE_Failure, CPLE_OutOfMemory,
344 : "BSBOpen : Out of memory. Probably due to corrupted BSB file (iPCT = %d).",
345 : iPCT);
346 0 : BSBClose( psInfo );
347 0 : return NULL;
348 : }
349 635 : psInfo->pabyPCT = pabyNewPCT;
350 635 : memset( psInfo->pabyPCT + psInfo->nPCTSize*3, 0,
351 635 : (iPCT+1-psInfo->nPCTSize) * 3);
352 635 : psInfo->nPCTSize = iPCT+1;
353 : }
354 :
355 635 : psInfo->pabyPCT[iPCT*3+0] = (unsigned char)atoi(papszTokens[1]);
356 635 : psInfo->pabyPCT[iPCT*3+1] = (unsigned char)atoi(papszTokens[2]);
357 635 : psInfo->pabyPCT[iPCT*3+2] = (unsigned char)atoi(papszTokens[3]);
358 : }
359 15 : else if( EQUALN(szLine,"VER/",4) && nCount >= 1 )
360 : {
361 5 : psInfo->nVersion = (int) (100 * atof(papszTokens[0]) + 0.5);
362 : }
363 :
364 655 : CSLDestroy( papszTokens );
365 : }
366 :
367 : /* -------------------------------------------------------------------- */
368 : /* Verify we found required keywords. */
369 : /* -------------------------------------------------------------------- */
370 5 : if( psInfo->nXSize == 0 || psInfo->nPCTSize == 0 )
371 : {
372 0 : BSBClose( psInfo );
373 0 : CPLError( CE_Failure, CPLE_AppDefined,
374 : "Failed to find required RGB/ or BSB/ keyword in header." );
375 :
376 0 : return NULL;
377 : }
378 :
379 5 : if( psInfo->nXSize <= 0 || psInfo->nYSize <= 0 )
380 : {
381 0 : BSBClose( psInfo );
382 0 : CPLError( CE_Failure, CPLE_AppDefined,
383 : "Wrong dimensions found in header : %d x %d.",
384 : psInfo->nXSize, psInfo->nYSize );
385 0 : return NULL;
386 : }
387 :
388 5 : if( psInfo->nVersion == 0 )
389 : {
390 0 : CPLError( CE_Warning, CPLE_AppDefined,
391 : "VER (version) keyword not found, assuming 2.0." );
392 0 : psInfo->nVersion = 200;
393 : }
394 :
395 : /* -------------------------------------------------------------------- */
396 : /* If all has gone well this far, we should be pointing at the */
397 : /* sequence "0x1A 0x00". Read past to get to start of data. */
398 : /* */
399 : /* We actually do some funny stuff here to be able to read past */
400 : /* some garbage to try and find the 0x1a 0x00 sequence since in */
401 : /* at least some files (ie. optech/World.kap) we find a few */
402 : /* bytes of extra junk in the way. */
403 : /* -------------------------------------------------------------------- */
404 : /* from optech/World.kap
405 :
406 : 11624: 30333237 34353938 2C302E30 35373836 03274598,0.05786
407 : 11640: 39303232 38332C31 332E3135 39363435 902283,13.159645
408 : 11656: 35390D0A 1A0D0A1A 00040190 C0510002 59~~~~~~~~~~~Q~~
409 : 11672: 90C05100 0390C051 000490C0 51000590 ~~Q~~~~Q~~~~Q~~~
410 : */
411 :
412 : {
413 5 : int nChar = -1;
414 :
415 20 : while( nSkipped < 100
416 : && (BSBGetc( psInfo, bNO1, &bErrorFlag ) != 0x1A
417 10 : || (nChar = BSBGetc( psInfo, bNO1, &bErrorFlag )) != 0x00)
418 0 : && !bErrorFlag)
419 : {
420 0 : if( nChar == 0x1A )
421 : {
422 0 : BSBUngetc( psInfo, nChar );
423 0 : nChar = -1;
424 : }
425 0 : nSkipped++;
426 : }
427 :
428 5 : if( bErrorFlag )
429 : {
430 0 : BSBClose( psInfo );
431 0 : CPLError( CE_Failure, CPLE_FileIO,
432 : "Truncated BSB file or I/O error." );
433 0 : return NULL;
434 : }
435 :
436 5 : if( nSkipped == 100 )
437 : {
438 0 : BSBClose( psInfo );
439 0 : CPLError( CE_Failure, CPLE_AppDefined,
440 : "Failed to find compressed data segment of BSB file." );
441 0 : return NULL;
442 : }
443 : }
444 :
445 : /* -------------------------------------------------------------------- */
446 : /* Read the number of bit size of color numbers. */
447 : /* -------------------------------------------------------------------- */
448 5 : psInfo->nColorSize = BSBGetc( psInfo, bNO1, NULL );
449 :
450 : /* The USGS files like 83116_1.KAP seem to use the ASCII number instead
451 : of the binary number for the colorsize value. */
452 :
453 5 : if( nSkipped > 0
454 0 : && psInfo->nColorSize >= 0x31 && psInfo->nColorSize <= 0x38 )
455 0 : psInfo->nColorSize -= 0x30;
456 :
457 5 : if( ! (psInfo->nColorSize > 0 && psInfo->nColorSize < 9) )
458 : {
459 0 : CPLError( CE_Failure, CPLE_AppDefined,
460 : "BSBOpen : Bad value for nColorSize (%d). Probably due to corrupted BSB file",
461 : psInfo->nColorSize );
462 0 : BSBClose( psInfo );
463 0 : return NULL;
464 : }
465 :
466 : /* -------------------------------------------------------------------- */
467 : /* Initialize memory for line offset list. */
468 : /* -------------------------------------------------------------------- */
469 5 : psInfo->panLineOffset = (int *)
470 5 : VSIMalloc2(sizeof(int), psInfo->nYSize);
471 5 : if (psInfo->panLineOffset == NULL)
472 : {
473 0 : CPLError( CE_Failure, CPLE_OutOfMemory,
474 : "BSBOpen : Out of memory. Probably due to corrupted BSB file (nYSize = %d).",
475 : psInfo->nYSize );
476 0 : BSBClose( psInfo );
477 0 : return NULL;
478 : }
479 :
480 : /* This is the offset to the data of first line, if there is no index table */
481 5 : nOffsetFirstLine = (int)(VSIFTellL( fp ) - psInfo->nBufferSize) + psInfo->nBufferOffset;
482 :
483 : /* -------------------------------------------------------------------- */
484 : /* Read the line offset list */
485 : /* -------------------------------------------------------------------- */
486 5 : if ( ! CSLTestBoolean(CPLGetConfigOption("BSB_DISABLE_INDEX", "NO")) )
487 : {
488 : /* build the list from file's index table */
489 : /* To overcome endian compatibility issues individual
490 : * bytes are being read instead of the whole integers. */
491 : int nVal;
492 5 : int listIsOK = 1;
493 : int nOffsetIndexTable;
494 : int nFileLen;
495 :
496 : /* Seek fp to point the last 4 byte integer which points
497 : * the offset of the first line */
498 5 : VSIFSeekL( fp, 0, SEEK_END );
499 5 : nFileLen = (int)VSIFTellL( fp );
500 5 : VSIFSeekL( fp, nFileLen - 4, SEEK_SET );
501 :
502 5 : VSIFReadL(&nVal, 1, 4, fp);//last 4 bytes
503 5 : CPL_MSBPTR32(&nVal);
504 5 : nOffsetIndexTable = nVal;
505 :
506 : /* For some charts, like 1115A_1.KAP, coming from */
507 : /* http://www.nauticalcharts.noaa.gov/mcd/Raster/index.htm, */
508 : /* the index table can have one row less than nYSize */
509 : /* If we look into the file closely, there is no data for */
510 : /* that last row (the end of line psInfo->nYSize - 1 is the start */
511 : /* of the index table), so we can decrement psInfo->nYSize */
512 5 : if (nOffsetIndexTable + 4 * (psInfo->nYSize - 1) == nFileLen - 4)
513 : {
514 0 : CPLDebug("BSB", "Index size is one row shorter than declared image height. Correct this");
515 0 : psInfo->nYSize --;
516 : }
517 :
518 10 : if( nOffsetIndexTable <= nOffsetFirstLine ||
519 5 : nOffsetIndexTable + 4 * psInfo->nYSize > nFileLen - 4)
520 : {
521 : /* The last 4 bytes are not the value of the offset to the index table */
522 : }
523 1 : else if (VSIFSeekL( fp, nOffsetIndexTable, SEEK_SET ) != 0 )
524 : {
525 0 : CPLError( CE_Failure, CPLE_FileIO,
526 : "Seek to offset 0x%08x for first line offset failed.",
527 : nOffsetIndexTable);
528 : }
529 : else
530 : {
531 1 : int nIndexSize = (nFileLen - 4 - nOffsetIndexTable) / 4;
532 1 : if (nIndexSize != psInfo->nYSize)
533 : {
534 0 : CPLDebug("BSB", "Index size is %d. Expected %d",
535 : nIndexSize, psInfo->nYSize);
536 : }
537 :
538 51 : for(i=0; i < psInfo->nYSize; i++)
539 : {
540 50 : VSIFReadL(&nVal, 1, 4, fp);
541 50 : CPL_MSBPTR32(&nVal);
542 50 : psInfo->panLineOffset[i] = nVal;
543 : }
544 : /* Simple checks for the integrity of the list */
545 51 : for(i=0; i < psInfo->nYSize; i++)
546 : {
547 249 : if( psInfo->panLineOffset[i] < nOffsetFirstLine ||
548 50 : psInfo->panLineOffset[i] >= nOffsetIndexTable ||
549 99 : (i < psInfo->nYSize - 1 && psInfo->panLineOffset[i] > psInfo->panLineOffset[i+1]) ||
550 50 : !BSBSeekAndCheckScanlineNumber(psInfo, i, FALSE) )
551 : {
552 0 : CPLDebug("BSB", "Index table is invalid at index %d", i);
553 0 : listIsOK = 0;
554 0 : break;
555 : }
556 : }
557 1 : if ( listIsOK )
558 : {
559 1 : CPLDebug("BSB", "Index table is valid");
560 1 : return psInfo;
561 : }
562 : }
563 : }
564 :
565 : /* If we can't build the offset list for some reason we just
566 : * initialize the offset list to indicate "no value" (except for the first). */
567 4 : psInfo->panLineOffset[0] = nOffsetFirstLine;
568 200 : for( i = 1; i < psInfo->nYSize; i++ )
569 196 : psInfo->panLineOffset[i] = -1;
570 :
571 4 : return psInfo;
572 : }
573 :
574 : /************************************************************************/
575 : /* BSBReadHeaderLine() */
576 : /* */
577 : /* Read one virtual line of text from the BSB header. This */
578 : /* will end if a 0x1A (EOF) is encountered, indicating the data */
579 : /* is about to start. It will also merge multiple physical */
580 : /* lines where appropriate. */
581 : /************************************************************************/
582 :
583 660 : static int BSBReadHeaderLine( BSBInfo *psInfo, char* pszLine, int nLineMaxLen, int bNO1 )
584 :
585 : {
586 : char chNext;
587 660 : int nLineLen = 0;
588 :
589 12515 : while( !VSIFEofL(psInfo->fp) && nLineLen < nLineMaxLen-1 )
590 : {
591 11855 : chNext = (char) BSBGetc( psInfo, bNO1, NULL );
592 11855 : if( chNext == 0x1A )
593 : {
594 5 : BSBUngetc( psInfo, chNext );
595 5 : return FALSE;
596 : }
597 :
598 : /* each CR/LF (or LF/CR) as if just "CR" */
599 11850 : if( chNext == 10 || chNext == 13 )
600 : {
601 : char chLF;
602 :
603 665 : chLF = (char) BSBGetc( psInfo, bNO1, NULL );
604 665 : if( chLF != 10 && chLF != 13 )
605 665 : BSBUngetc( psInfo, chLF );
606 665 : chNext = '\n';
607 : }
608 :
609 : /* If we are at the end-of-line, check for blank at start
610 : ** of next line, to indicate need of continuation.
611 : */
612 11850 : if( chNext == '\n' )
613 : {
614 : char chTest;
615 :
616 665 : chTest = (char) BSBGetc(psInfo, bNO1, NULL);
617 : /* Are we done? */
618 665 : if( chTest != ' ' )
619 : {
620 655 : BSBUngetc( psInfo, chTest );
621 655 : pszLine[nLineLen] = '\0';
622 655 : return TRUE;
623 : }
624 :
625 : /* eat pending spaces */
626 65 : while( chTest == ' ' )
627 45 : chTest = (char) BSBGetc(psInfo,bNO1, NULL);
628 10 : BSBUngetc( psInfo,chTest );
629 :
630 : /* insert comma in data stream */
631 10 : pszLine[nLineLen++] = ',';
632 : }
633 : else
634 : {
635 11185 : pszLine[nLineLen++] = chNext;
636 : }
637 : }
638 :
639 0 : return FALSE;
640 : }
641 :
642 : /************************************************************************/
643 : /* BSBSeekAndCheckScanlineNumber() */
644 : /* */
645 : /* Seek to the beginning of the scanline and check that the */
646 : /* scanline number in file is consistant with what we expect */
647 : /* */
648 : /* @param nScanline zero based line number */
649 : /************************************************************************/
650 :
651 301 : static int BSBSeekAndCheckScanlineNumber ( BSBInfo *psInfo, int nScanline,
652 : int bVerboseIfError )
653 : {
654 301 : int nLineMarker = 0;
655 : int byNext;
656 301 : VSILFILE *fp = psInfo->fp;
657 301 : int bErrorFlag = FALSE;
658 :
659 : /* -------------------------------------------------------------------- */
660 : /* Seek to requested scanline. */
661 : /* -------------------------------------------------------------------- */
662 301 : psInfo->nBufferSize = 0;
663 301 : if( VSIFSeekL( fp, psInfo->panLineOffset[nScanline], SEEK_SET ) != 0 )
664 : {
665 0 : if (bVerboseIfError)
666 : {
667 0 : CPLError( CE_Failure, CPLE_FileIO,
668 : "Seek to offset %d for scanline %d failed.",
669 0 : psInfo->panLineOffset[nScanline], nScanline );
670 : }
671 : else
672 : {
673 0 : CPLDebug("BSB", "Seek to offset %d for scanline %d failed.",
674 0 : psInfo->panLineOffset[nScanline], nScanline );
675 : }
676 0 : return FALSE;
677 : }
678 :
679 : /* -------------------------------------------------------------------- */
680 : /* Read the line number. Pre 2.0 BSB seemed to expect the line */
681 : /* numbers to be zero based, while 2.0 and later seemed to */
682 : /* expect it to be one based, and for a 0 to be some sort of */
683 : /* missing line marker. */
684 : /* -------------------------------------------------------------------- */
685 : do {
686 301 : byNext = BSBGetc( psInfo, psInfo->bNO1, &bErrorFlag );
687 :
688 : /* Special hack to skip over extra zeros in some files, such
689 : ** as optech/sample1.kap.
690 : */
691 602 : while( nScanline != 0 && nLineMarker == 0 && byNext == 0 && !bErrorFlag )
692 0 : byNext = BSBGetc( psInfo, psInfo->bNO1, &bErrorFlag );
693 :
694 301 : nLineMarker = nLineMarker * 128 + (byNext & 0x7f);
695 301 : } while( (byNext & 0x80) != 0 );
696 :
697 301 : if ( bErrorFlag )
698 : {
699 1 : if (bVerboseIfError)
700 : {
701 1 : CPLError( CE_Failure, CPLE_FileIO,
702 : "Truncated BSB file or I/O error." );
703 : }
704 1 : return FALSE;
705 : }
706 600 : if( nLineMarker != nScanline
707 300 : && nLineMarker != nScanline + 1 )
708 : {
709 : int bIgnoreLineNumbers =
710 1 : CSLTestBoolean(CPLGetConfigOption("BSB_IGNORE_LINENUMBERS", "NO"));
711 :
712 1 : if (bVerboseIfError && !bIgnoreLineNumbers )
713 : {
714 0 : CPLError( CE_Failure, CPLE_AppDefined,
715 : "Got scanline id %d when looking for %d @ offset %d.\nSet BSB_IGNORE_LINENUMBERS=TRUE configuration option to try file anyways.",
716 0 : nLineMarker, nScanline+1, psInfo->panLineOffset[nScanline]);
717 : }
718 : else
719 : {
720 1 : CPLDebug("BSB", "Got scanline id %d when looking for %d @ offset %d.",
721 1 : nLineMarker, nScanline+1, psInfo->panLineOffset[nScanline]);
722 : }
723 :
724 1 : if( !bIgnoreLineNumbers )
725 1 : return FALSE;
726 : }
727 :
728 299 : return TRUE;
729 : }
730 :
731 : /************************************************************************/
732 : /* BSBReadScanline() */
733 : /* @param nScanline zero based line number */
734 : /************************************************************************/
735 :
736 250 : int BSBReadScanline( BSBInfo *psInfo, int nScanline,
737 : unsigned char *pabyScanlineBuf )
738 :
739 : {
740 250 : int nValueShift, iPixel = 0;
741 : unsigned char byValueMask, byCountMask;
742 250 : VSILFILE *fp = psInfo->fp;
743 : int byNext, i;
744 :
745 : /* -------------------------------------------------------------------- */
746 : /* Do we know where the requested line is? If not, read all */
747 : /* the preceeding ones to "find" our line. */
748 : /* -------------------------------------------------------------------- */
749 250 : if( nScanline < 0 || nScanline >= psInfo->nYSize )
750 : {
751 0 : CPLError( CE_Failure, CPLE_FileIO,
752 : "Scanline %d out of range.",
753 : nScanline );
754 0 : return FALSE;
755 : }
756 :
757 250 : if( psInfo->panLineOffset[nScanline] == -1 )
758 : {
759 0 : for( i = 0; i < nScanline; i++ )
760 : {
761 0 : if( psInfo->panLineOffset[i+1] == -1 )
762 : {
763 0 : if( !BSBReadScanline( psInfo, i, pabyScanlineBuf ) )
764 0 : return FALSE;
765 : }
766 : }
767 : }
768 :
769 : /* -------------------------------------------------------------------- */
770 : /* Seek to the beginning of the scanline and check that the */
771 : /* scanline number in file is consistant with what we expect */
772 : /* -------------------------------------------------------------------- */
773 250 : if ( !BSBSeekAndCheckScanlineNumber(psInfo, nScanline, TRUE) )
774 : {
775 1 : return FALSE;
776 : }
777 :
778 : /* -------------------------------------------------------------------- */
779 : /* Setup masking values. */
780 : /* -------------------------------------------------------------------- */
781 249 : nValueShift = 7 - psInfo->nColorSize;
782 249 : byValueMask = (unsigned char)
783 249 : ((((1 << psInfo->nColorSize)) - 1) << nValueShift);
784 249 : byCountMask = (unsigned char)
785 249 : (1 << (7 - psInfo->nColorSize)) - 1;
786 :
787 : /* -------------------------------------------------------------------- */
788 : /* Read and expand runs. */
789 : /* If for some reason the buffer is not filled, */
790 : /* just repeat the process until the buffer is filled. */
791 : /* This is the case for IS1612_4.NOS (#2782) */
792 : /* -------------------------------------------------------------------- */
793 : do
794 : {
795 250 : int bErrorFlag = FALSE;
796 25396 : while( (byNext = BSBGetc(psInfo,psInfo->bNO1, &bErrorFlag)) != 0 &&
797 12448 : !bErrorFlag)
798 : {
799 : int nPixValue;
800 : int nRunCount, i;
801 :
802 12448 : nPixValue = (byNext & byValueMask) >> nValueShift;
803 :
804 12448 : nRunCount = byNext & byCountMask;
805 :
806 24896 : while( (byNext & 0x80) != 0 && !bErrorFlag)
807 : {
808 0 : byNext = BSBGetc( psInfo, psInfo->bNO1, &bErrorFlag );
809 0 : nRunCount = nRunCount * 128 + (byNext & 0x7f);
810 : }
811 :
812 : /* Prevent over-run of line data */
813 12448 : if (nRunCount < 0 || nRunCount > INT_MAX - (iPixel + 1))
814 : {
815 0 : CPLError( CE_Failure, CPLE_FileIO,
816 : "Corrupted run count : %d", nRunCount );
817 0 : return FALSE;
818 : }
819 12448 : if (nRunCount > psInfo->nXSize)
820 : {
821 : static int bHasWarned = FALSE;
822 0 : if (!bHasWarned)
823 : {
824 0 : CPLDebug("BSB", "Too big run count : %d", nRunCount );
825 0 : bHasWarned = TRUE;
826 : }
827 : }
828 :
829 12448 : if( iPixel + nRunCount + 1 > psInfo->nXSize )
830 0 : nRunCount = psInfo->nXSize - iPixel - 1;
831 :
832 24896 : for( i = 0; i < nRunCount+1; i++ )
833 12448 : pabyScanlineBuf[iPixel++] = (unsigned char) nPixValue;
834 : }
835 250 : if ( bErrorFlag )
836 : {
837 1 : CPLError( CE_Failure, CPLE_FileIO,
838 : "Truncated BSB file or I/O error." );
839 1 : return FALSE;
840 : }
841 :
842 : /* -------------------------------------------------------------------- */
843 : /* For reasons that are unclear, some scanlines are exactly one */
844 : /* pixel short (such as in the BSB 3.0 354704.KAP product from */
845 : /* NDI/CHS) but are otherwise OK. Just add a zero if this */
846 : /* appear to have occured. */
847 : /* -------------------------------------------------------------------- */
848 249 : if( iPixel == psInfo->nXSize - 1 )
849 0 : pabyScanlineBuf[iPixel++] = 0;
850 :
851 : /* -------------------------------------------------------------------- */
852 : /* If we have not enough data and no offset table, check that the */
853 : /* next bytes are not the expected next scanline number. If they are */
854 : /* not, then we can use them to fill the row again */
855 : /* -------------------------------------------------------------------- */
856 251 : else if (iPixel < psInfo->nXSize &&
857 1 : nScanline != psInfo->nYSize-1 &&
858 1 : psInfo->panLineOffset[nScanline+1] == -1)
859 : {
860 1 : int nCurOffset = (int)(VSIFTellL( fp ) - psInfo->nBufferSize) +
861 1 : psInfo->nBufferOffset;
862 1 : psInfo->panLineOffset[nScanline+1] = nCurOffset;
863 1 : if (BSBSeekAndCheckScanlineNumber(psInfo, nScanline + 1, FALSE))
864 : {
865 0 : CPLDebug("BSB", "iPixel=%d, nScanline=%d, nCurOffset=%d --> found new row marker", iPixel, nScanline, nCurOffset);
866 0 : break;
867 : }
868 : else
869 : {
870 1 : CPLDebug("BSB", "iPixel=%d, nScanline=%d, nCurOffset=%d --> did NOT find new row marker", iPixel, nScanline, nCurOffset);
871 :
872 : /* The next bytes are not the expected next scanline number, so */
873 : /* use them to fill the row */
874 1 : VSIFSeekL( fp, nCurOffset, SEEK_SET );
875 1 : psInfo->panLineOffset[nScanline+1] = -1;
876 1 : psInfo->nBufferOffset = 0;
877 1 : psInfo->nBufferSize = 0;
878 : }
879 : }
880 : }
881 249 : while ( iPixel < psInfo->nXSize &&
882 1 : (nScanline == psInfo->nYSize-1 ||
883 1 : psInfo->panLineOffset[nScanline+1] == -1 ||
884 251 : VSIFTellL( fp ) - psInfo->nBufferSize + psInfo->nBufferOffset < psInfo->panLineOffset[nScanline+1]) );
885 :
886 : /* -------------------------------------------------------------------- */
887 : /* If the line buffer is not filled after reading the line in the */
888 : /* file upto the next line offset, just fill it with zeros. */
889 : /* (The last pixel value from nPixValue could be a better value?) */
890 : /* -------------------------------------------------------------------- */
891 496 : while( iPixel < psInfo->nXSize )
892 0 : pabyScanlineBuf[iPixel++] = 0;
893 :
894 : /* -------------------------------------------------------------------- */
895 : /* Remember the start of the next line. */
896 : /* But only if it is not already known. */
897 : /* -------------------------------------------------------------------- */
898 493 : if( nScanline < psInfo->nYSize-1 &&
899 245 : psInfo->panLineOffset[nScanline+1] == -1 )
900 : {
901 196 : psInfo->panLineOffset[nScanline+1] = (int)
902 : (VSIFTellL( fp ) - psInfo->nBufferSize) + psInfo->nBufferOffset;
903 : }
904 :
905 248 : return TRUE;
906 : }
907 :
908 : /************************************************************************/
909 : /* BSBClose() */
910 : /************************************************************************/
911 :
912 5 : void BSBClose( BSBInfo *psInfo )
913 :
914 : {
915 5 : if( psInfo->fp != NULL )
916 5 : VSIFCloseL( psInfo->fp );
917 :
918 5 : CPLFree( psInfo->pabyBuffer );
919 :
920 5 : CSLDestroy( psInfo->papszHeader );
921 5 : CPLFree( psInfo->panLineOffset );
922 5 : CPLFree( psInfo->pabyPCT );
923 5 : CPLFree( psInfo );
924 5 : }
925 :
926 : /************************************************************************/
927 : /* BSBCreate() */
928 : /************************************************************************/
929 :
930 0 : BSBInfo *BSBCreate( const char *pszFilename, int nCreationFlags, int nVersion,
931 : int nXSize, int nYSize )
932 :
933 : {
934 : VSILFILE *fp;
935 : BSBInfo *psInfo;
936 :
937 : /* -------------------------------------------------------------------- */
938 : /* Open new KAP file. */
939 : /* -------------------------------------------------------------------- */
940 0 : fp = VSIFOpenL( pszFilename, "wb" );
941 0 : if( fp == NULL )
942 : {
943 0 : CPLError( CE_Failure, CPLE_OpenFailed,
944 : "Failed to open output file %s.",
945 : pszFilename );
946 0 : return NULL;
947 : }
948 :
949 : /* -------------------------------------------------------------------- */
950 : /* Write out BSB line. */
951 : /* -------------------------------------------------------------------- */
952 0 : VSIFPrintfL( fp,
953 : "!Copyright unknown\n" );
954 0 : VSIFPrintfL( fp,
955 : "VER/%.1f\n", nVersion / 100.0 );
956 0 : VSIFPrintfL( fp,
957 : "BSB/NA=UNKNOWN,NU=999502,RA=%d,%d,DU=254\n",
958 : nXSize, nYSize );
959 0 : VSIFPrintfL( fp,
960 : "KNP/SC=25000,GD=WGS84,PR=Mercator\n" );
961 0 : VSIFPrintfL( fp,
962 : " PP=31.500000,PI=0.033333,SP=,SK=0.000000,TA=90.000000\n");
963 0 : VSIFPrintfL( fp,
964 : " UN=Metres,SD=HHWLT,DX=2.500000,DY=2.500000\n");
965 :
966 :
967 : /* -------------------------------------------------------------------- */
968 : /* Create info structure. */
969 : /* -------------------------------------------------------------------- */
970 0 : psInfo = (BSBInfo *) CPLCalloc(1,sizeof(BSBInfo));
971 0 : psInfo->fp = fp;
972 0 : psInfo->bNO1 = FALSE;
973 0 : psInfo->nVersion = nVersion;
974 0 : psInfo->nXSize = nXSize;
975 0 : psInfo->nYSize = nYSize;
976 0 : psInfo->bNewFile = TRUE;
977 0 : psInfo->nLastLineWritten = -1;
978 :
979 0 : return psInfo;
980 : }
981 :
982 : /************************************************************************/
983 : /* BSBWritePCT() */
984 : /************************************************************************/
985 :
986 0 : int BSBWritePCT( BSBInfo *psInfo, int nPCTSize, unsigned char *pabyPCT )
987 :
988 : {
989 : int i;
990 :
991 : /* -------------------------------------------------------------------- */
992 : /* Verify the PCT not too large. */
993 : /* -------------------------------------------------------------------- */
994 0 : if( nPCTSize > 128 )
995 : {
996 0 : CPLError( CE_Failure, CPLE_AppDefined,
997 : "Pseudo-color table too large (%d entries), at most 128\n"
998 : " entries allowed in BSB format.", nPCTSize );
999 0 : return FALSE;
1000 : }
1001 :
1002 : /* -------------------------------------------------------------------- */
1003 : /* Compute the number of bits required for the colors. */
1004 : /* -------------------------------------------------------------------- */
1005 0 : for( psInfo->nColorSize = 1;
1006 0 : (1 << psInfo->nColorSize) < nPCTSize;
1007 0 : psInfo->nColorSize++ ) {}
1008 :
1009 : /* -------------------------------------------------------------------- */
1010 : /* Write out the color table. Note that color table entry zero */
1011 : /* is ignored. Zero is not a legal value. */
1012 : /* -------------------------------------------------------------------- */
1013 0 : for( i = 1; i < nPCTSize; i++ )
1014 : {
1015 0 : VSIFPrintfL( psInfo->fp,
1016 : "RGB/%d,%d,%d,%d\n",
1017 0 : i, pabyPCT[i*3+0], pabyPCT[i*3+1], pabyPCT[i*3+2] );
1018 : }
1019 :
1020 0 : return TRUE;
1021 : }
1022 :
1023 : /************************************************************************/
1024 : /* BSBWriteScanline() */
1025 : /************************************************************************/
1026 :
1027 0 : int BSBWriteScanline( BSBInfo *psInfo, unsigned char *pabyScanlineBuf )
1028 :
1029 : {
1030 : int nValue, iX;
1031 :
1032 0 : if( psInfo->nLastLineWritten == psInfo->nYSize - 1 )
1033 : {
1034 0 : CPLError( CE_Failure, CPLE_AppDefined,
1035 : "Attempt to write too many scanlines." );
1036 0 : return FALSE;
1037 : }
1038 :
1039 : /* -------------------------------------------------------------------- */
1040 : /* If this is the first scanline writen out the EOF marker, and */
1041 : /* the introductory info in the image segment. */
1042 : /* -------------------------------------------------------------------- */
1043 0 : if( psInfo->nLastLineWritten == -1 )
1044 : {
1045 0 : VSIFPutcL( 0x1A, psInfo->fp );
1046 0 : VSIFPutcL( 0x00, psInfo->fp );
1047 0 : VSIFPutcL( psInfo->nColorSize, psInfo->fp );
1048 : }
1049 :
1050 : /* -------------------------------------------------------------------- */
1051 : /* Write the line number. */
1052 : /* -------------------------------------------------------------------- */
1053 0 : nValue = ++psInfo->nLastLineWritten;
1054 :
1055 0 : if( psInfo->nVersion >= 200 )
1056 0 : nValue++;
1057 :
1058 0 : if( nValue >= 128*128 )
1059 0 : VSIFPutcL( 0x80 | ((nValue & (0x7f<<14)) >> 14), psInfo->fp );
1060 0 : if( nValue >= 128 )
1061 0 : VSIFPutcL( 0x80 | ((nValue & (0x7f<<7)) >> 7), psInfo->fp );
1062 0 : VSIFPutcL( nValue & 0x7f, psInfo->fp );
1063 :
1064 : /* -------------------------------------------------------------------- */
1065 : /* Write out each pixel as a separate byte. We don't try to */
1066 : /* actually capture the runs since that radical and futuristic */
1067 : /* concept is patented! */
1068 : /* -------------------------------------------------------------------- */
1069 0 : for( iX = 0; iX < psInfo->nXSize; iX++ )
1070 : {
1071 0 : VSIFPutcL( pabyScanlineBuf[iX] << (7-psInfo->nColorSize),
1072 : psInfo->fp );
1073 : }
1074 :
1075 0 : VSIFPutcL( 0x00, psInfo->fp );
1076 :
1077 0 : return TRUE;
1078 : }
|