1 : /******************************************************************************
2 : * $Id: bsb_read.c 17405 2009-07-17 06:13:24Z chaitanya $
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 17405 2009-07-17 06:13:24Z chaitanya $");
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 : 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 : FILE *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 655 : 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 :
276 655 : if( EQUALN(szLine,"BSB/",4) )
277 : {
278 : int nRAIndex;
279 :
280 5 : nRAIndex = CSLFindString(papszTokens, "RA" );
281 5 : if( nRAIndex < 0 || nRAIndex+2 >= nCount )
282 : {
283 0 : CSLDestroy( papszTokens );
284 0 : CPLError( CE_Failure, CPLE_AppDefined,
285 : "Failed to extract RA from BSB/ line." );
286 0 : BSBClose( psInfo );
287 0 : return NULL;
288 : }
289 5 : psInfo->nXSize = atoi(papszTokens[nRAIndex+1]);
290 5 : psInfo->nYSize = atoi(papszTokens[nRAIndex+2]);
291 : }
292 650 : else if( EQUALN(szLine,"NOS/",4) )
293 : {
294 : int nRAIndex;
295 :
296 0 : nRAIndex = CSLFindString(papszTokens, "RA" );
297 0 : if( nRAIndex < 0 || nRAIndex+4 >= nCount )
298 : {
299 0 : CSLDestroy( papszTokens );
300 0 : CPLError( CE_Failure, CPLE_AppDefined,
301 : "Failed to extract RA from NOS/ line." );
302 0 : BSBClose( psInfo );
303 0 : return NULL;
304 : }
305 0 : psInfo->nXSize = atoi(papszTokens[nRAIndex+3]);
306 0 : psInfo->nYSize = atoi(papszTokens[nRAIndex+4]);
307 : }
308 1285 : else if( EQUALN(szLine, pszPalette, 3) && szLine[3] == '/'
309 : && nCount >= 4 )
310 : {
311 635 : int iPCT = atoi(papszTokens[0]);
312 635 : if (iPCT < 0 || iPCT > 128)
313 : {
314 0 : CSLDestroy( papszTokens );
315 0 : CPLError( CE_Failure, CPLE_OutOfMemory,
316 : "BSBOpen : Invalid color table index. Probably due to corrupted BSB file (iPCT = %d).",
317 : iPCT);
318 0 : BSBClose( psInfo );
319 0 : return NULL;
320 : }
321 635 : if( iPCT > psInfo->nPCTSize-1 )
322 : {
323 1270 : unsigned char* pabyNewPCT = (unsigned char *)
324 1905 : VSIRealloc(psInfo->pabyPCT,(iPCT+1) * 3);
325 635 : if (pabyNewPCT == NULL)
326 : {
327 0 : CSLDestroy( papszTokens );
328 0 : CPLError( CE_Failure, CPLE_OutOfMemory,
329 : "BSBOpen : Out of memory. Probably due to corrupted BSB file (iPCT = %d).",
330 : iPCT);
331 0 : BSBClose( psInfo );
332 0 : return NULL;
333 : }
334 635 : psInfo->pabyPCT = pabyNewPCT;
335 635 : memset( psInfo->pabyPCT + psInfo->nPCTSize*3, 0,
336 635 : (iPCT+1-psInfo->nPCTSize) * 3);
337 635 : psInfo->nPCTSize = iPCT+1;
338 : }
339 :
340 635 : psInfo->pabyPCT[iPCT*3+0] = (unsigned char)atoi(papszTokens[1]);
341 635 : psInfo->pabyPCT[iPCT*3+1] = (unsigned char)atoi(papszTokens[2]);
342 635 : psInfo->pabyPCT[iPCT*3+2] = (unsigned char)atoi(papszTokens[3]);
343 : }
344 15 : else if( EQUALN(szLine,"VER/",4) && nCount >= 1 )
345 : {
346 5 : psInfo->nVersion = (int) (100 * atof(papszTokens[0]) + 0.5);
347 : }
348 :
349 655 : CSLDestroy( papszTokens );
350 : }
351 :
352 : /* -------------------------------------------------------------------- */
353 : /* Verify we found required keywords. */
354 : /* -------------------------------------------------------------------- */
355 5 : if( psInfo->nXSize == 0 || psInfo->nPCTSize == 0 )
356 : {
357 0 : BSBClose( psInfo );
358 0 : CPLError( CE_Failure, CPLE_AppDefined,
359 : "Failed to find required RGB/ or BSB/ keyword in header." );
360 :
361 0 : return NULL;
362 : }
363 :
364 5 : if( psInfo->nXSize <= 0 || psInfo->nYSize <= 0 )
365 : {
366 0 : BSBClose( psInfo );
367 0 : CPLError( CE_Failure, CPLE_AppDefined,
368 : "Wrong dimensions found in header : %d x %d.",
369 : psInfo->nXSize, psInfo->nYSize );
370 0 : return NULL;
371 : }
372 :
373 5 : if( psInfo->nVersion == 0 )
374 : {
375 0 : CPLError( CE_Warning, CPLE_AppDefined,
376 : "VER (version) keyword not found, assuming 2.0." );
377 0 : psInfo->nVersion = 200;
378 : }
379 :
380 : /* -------------------------------------------------------------------- */
381 : /* If all has gone well this far, we should be pointing at the */
382 : /* sequence "0x1A 0x00". Read past to get to start of data. */
383 : /* */
384 : /* We actually do some funny stuff here to be able to read past */
385 : /* some garbage to try and find the 0x1a 0x00 sequence since in */
386 : /* at least some files (ie. optech/World.kap) we find a few */
387 : /* bytes of extra junk in the way. */
388 : /* -------------------------------------------------------------------- */
389 : /* from optech/World.kap
390 :
391 : 11624: 30333237 34353938 2C302E30 35373836 03274598,0.05786
392 : 11640: 39303232 38332C31 332E3135 39363435 902283,13.159645
393 : 11656: 35390D0A 1A0D0A1A 00040190 C0510002 59~~~~~~~~~~~Q~~
394 : 11672: 90C05100 0390C051 000490C0 51000590 ~~Q~~~~Q~~~~Q~~~
395 : */
396 :
397 : {
398 5 : int nChar = -1;
399 :
400 20 : while( nSkipped < 100
401 : && (BSBGetc( psInfo, bNO1, &bErrorFlag ) != 0x1A
402 10 : || (nChar = BSBGetc( psInfo, bNO1, &bErrorFlag )) != 0x00)
403 0 : && !bErrorFlag)
404 : {
405 0 : if( nChar == 0x1A )
406 : {
407 0 : BSBUngetc( psInfo, nChar );
408 0 : nChar = -1;
409 : }
410 0 : nSkipped++;
411 : }
412 :
413 5 : if( bErrorFlag )
414 : {
415 0 : BSBClose( psInfo );
416 0 : CPLError( CE_Failure, CPLE_FileIO,
417 : "Truncated BSB file or I/O error." );
418 0 : return NULL;
419 : }
420 :
421 5 : if( nSkipped == 100 )
422 : {
423 0 : BSBClose( psInfo );
424 0 : CPLError( CE_Failure, CPLE_AppDefined,
425 : "Failed to find compressed data segment of BSB file." );
426 0 : return NULL;
427 : }
428 : }
429 :
430 : /* -------------------------------------------------------------------- */
431 : /* Read the number of bit size of color numbers. */
432 : /* -------------------------------------------------------------------- */
433 5 : psInfo->nColorSize = BSBGetc( psInfo, bNO1, NULL );
434 :
435 : /* The USGS files like 83116_1.KAP seem to use the ASCII number instead
436 : of the binary number for the colorsize value. */
437 :
438 5 : if( nSkipped > 0
439 0 : && psInfo->nColorSize >= 0x31 && psInfo->nColorSize <= 0x38 )
440 0 : psInfo->nColorSize -= 0x30;
441 :
442 5 : if( ! (psInfo->nColorSize > 0 && psInfo->nColorSize < 9) )
443 : {
444 0 : CPLError( CE_Failure, CPLE_AppDefined,
445 : "BSBOpen : Bad value for nColorSize (%d). Probably due to corrupted BSB file",
446 : psInfo->nColorSize );
447 0 : BSBClose( psInfo );
448 0 : return NULL;
449 : }
450 :
451 : /* -------------------------------------------------------------------- */
452 : /* Initialize memory for line offset list. */
453 : /* -------------------------------------------------------------------- */
454 5 : psInfo->panLineOffset = (int *)
455 5 : VSIMalloc2(sizeof(int), psInfo->nYSize);
456 5 : if (psInfo->panLineOffset == NULL)
457 : {
458 0 : CPLError( CE_Failure, CPLE_OutOfMemory,
459 : "BSBOpen : Out of memory. Probably due to corrupted BSB file (nYSize = %d).",
460 : psInfo->nYSize );
461 0 : BSBClose( psInfo );
462 0 : return NULL;
463 : }
464 :
465 : /* This is the offset to the data of first line, if there is no index table */
466 5 : nOffsetFirstLine = (int)(VSIFTellL( fp ) - psInfo->nBufferSize) + psInfo->nBufferOffset;
467 :
468 : /* -------------------------------------------------------------------- */
469 : /* Read the line offset list */
470 : /* -------------------------------------------------------------------- */
471 5 : if ( ! CSLTestBoolean(CPLGetConfigOption("BSB_DISABLE_INDEX", "NO")) )
472 : {
473 : /* build the list from file's index table */
474 : /* To overcome endian compatibility issues individual
475 : * bytes are being read instead of the whole integers. */
476 : int nVal;
477 5 : int listIsOK = 1;
478 : int nOffsetIndexTable;
479 : int nFileLen;
480 :
481 : /* Seek fp to point the last 4 byte integer which points
482 : * the offset of the first line */
483 5 : VSIFSeekL( fp, 0, SEEK_END );
484 5 : nFileLen = (int)VSIFTellL( fp );
485 5 : VSIFSeekL( fp, nFileLen - 4, SEEK_SET );
486 :
487 5 : VSIFReadL(&nVal, 1, 4, fp);//last 4 bytes
488 5 : CPL_MSBPTR32(&nVal);
489 5 : nOffsetIndexTable = nVal;
490 :
491 : /* For some charts, like 1115A_1.KAP, coming from */
492 : /* http://www.nauticalcharts.noaa.gov/mcd/Raster/index.htm, */
493 : /* the index table can have one row less than nYSize */
494 : /* If we look into the file closely, there is no data for */
495 : /* that last row (the end of line psInfo->nYSize - 1 is the start */
496 : /* of the index table), so we can decrement psInfo->nYSize */
497 5 : if (nOffsetIndexTable + 4 * (psInfo->nYSize - 1) == nFileLen - 4)
498 : {
499 0 : CPLDebug("BSB", "Index size is one row shorter than declared image height. Correct this");
500 0 : psInfo->nYSize --;
501 : }
502 :
503 10 : if( nOffsetIndexTable <= nOffsetFirstLine ||
504 5 : nOffsetIndexTable + 4 * psInfo->nYSize > nFileLen - 4)
505 : {
506 : /* The last 4 bytes are not the value of the offset to the index table */
507 : }
508 1 : else if (VSIFSeekL( fp, nOffsetIndexTable, SEEK_SET ) != 0 )
509 : {
510 0 : CPLError( CE_Failure, CPLE_FileIO,
511 : "Seek to offset 0x%08x for first line offset failed.",
512 : nOffsetIndexTable);
513 : }
514 : else
515 : {
516 1 : int nIndexSize = (nFileLen - 4 - nOffsetIndexTable) / 4;
517 1 : if (nIndexSize != psInfo->nYSize)
518 : {
519 0 : CPLDebug("BSB", "Index size is %d. Expected %d",
520 : nIndexSize, psInfo->nYSize);
521 : }
522 :
523 51 : for(i=0; i < psInfo->nYSize; i++)
524 : {
525 50 : VSIFReadL(&nVal, 1, 4, fp);
526 50 : CPL_MSBPTR32(&nVal);
527 50 : psInfo->panLineOffset[i] = nVal;
528 : }
529 : /* Simple checks for the integrity of the list */
530 51 : for(i=0; i < psInfo->nYSize; i++)
531 : {
532 249 : if( psInfo->panLineOffset[i] < nOffsetFirstLine ||
533 50 : psInfo->panLineOffset[i] >= nOffsetIndexTable ||
534 99 : (i < psInfo->nYSize - 1 && psInfo->panLineOffset[i] > psInfo->panLineOffset[i+1]) ||
535 50 : !BSBSeekAndCheckScanlineNumber(psInfo, i, FALSE) )
536 : {
537 0 : CPLDebug("BSB", "Index table is invalid at index %d", i);
538 0 : listIsOK = 0;
539 0 : break;
540 : }
541 : }
542 1 : if ( listIsOK )
543 : {
544 1 : CPLDebug("BSB", "Index table is valid");
545 1 : return psInfo;
546 : }
547 : }
548 : }
549 :
550 : /* If we can't build the offset list for some reason we just
551 : * initialize the offset list to indicate "no value" (except for the first). */
552 4 : psInfo->panLineOffset[0] = nOffsetFirstLine;
553 200 : for( i = 1; i < psInfo->nYSize; i++ )
554 196 : psInfo->panLineOffset[i] = -1;
555 :
556 4 : return psInfo;
557 : }
558 :
559 : /************************************************************************/
560 : /* BSBReadHeaderLine() */
561 : /* */
562 : /* Read one virtual line of text from the BSB header. This */
563 : /* will end if a 0x1A (EOF) is encountered, indicating the data */
564 : /* is about to start. It will also merge multiple physical */
565 : /* lines where appropriate. */
566 : /************************************************************************/
567 :
568 660 : static int BSBReadHeaderLine( BSBInfo *psInfo, char* pszLine, int nLineMaxLen, int bNO1 )
569 :
570 : {
571 : char chNext;
572 660 : int nLineLen = 0;
573 :
574 12515 : while( !VSIFEofL(psInfo->fp) && nLineLen < nLineMaxLen-1 )
575 : {
576 11855 : chNext = (char) BSBGetc( psInfo, bNO1, NULL );
577 11855 : if( chNext == 0x1A )
578 : {
579 5 : BSBUngetc( psInfo, chNext );
580 5 : return FALSE;
581 : }
582 :
583 : /* each CR/LF (or LF/CR) as if just "CR" */
584 11850 : if( chNext == 10 || chNext == 13 )
585 : {
586 : char chLF;
587 :
588 665 : chLF = (char) BSBGetc( psInfo, bNO1, NULL );
589 665 : if( chLF != 10 && chLF != 13 )
590 665 : BSBUngetc( psInfo, chLF );
591 665 : chNext = '\n';
592 : }
593 :
594 : /* If we are at the end-of-line, check for blank at start
595 : ** of next line, to indicate need of continuation.
596 : */
597 11850 : if( chNext == '\n' )
598 : {
599 : char chTest;
600 :
601 665 : chTest = (char) BSBGetc(psInfo, bNO1, NULL);
602 : /* Are we done? */
603 665 : if( chTest != ' ' )
604 : {
605 655 : BSBUngetc( psInfo, chTest );
606 655 : pszLine[nLineLen] = '\0';
607 655 : return TRUE;
608 : }
609 :
610 : /* eat pending spaces */
611 65 : while( chTest == ' ' )
612 45 : chTest = (char) BSBGetc(psInfo,bNO1, NULL);
613 10 : BSBUngetc( psInfo,chTest );
614 :
615 : /* insert comma in data stream */
616 10 : pszLine[nLineLen++] = ',';
617 : }
618 : else
619 : {
620 11185 : pszLine[nLineLen++] = chNext;
621 : }
622 : }
623 :
624 0 : return FALSE;
625 : }
626 :
627 : /************************************************************************/
628 : /* BSBSeekAndCheckScanlineNumber() */
629 : /* */
630 : /* Seek to the beginning of the scanline and check that the */
631 : /* scanline number in file is consistant with what we expect */
632 : /* */
633 : /* @param nScanline zero based line number */
634 : /************************************************************************/
635 :
636 301 : static int BSBSeekAndCheckScanlineNumber ( BSBInfo *psInfo, int nScanline,
637 : int bVerboseIfError )
638 : {
639 301 : int nLineMarker = 0;
640 : int byNext;
641 301 : FILE *fp = psInfo->fp;
642 301 : int bErrorFlag = FALSE;
643 :
644 : /* -------------------------------------------------------------------- */
645 : /* Seek to requested scanline. */
646 : /* -------------------------------------------------------------------- */
647 301 : psInfo->nBufferSize = 0;
648 301 : if( VSIFSeekL( fp, psInfo->panLineOffset[nScanline], SEEK_SET ) != 0 )
649 : {
650 0 : if (bVerboseIfError)
651 : {
652 0 : CPLError( CE_Failure, CPLE_FileIO,
653 : "Seek to offset %d for scanline %d failed.",
654 0 : psInfo->panLineOffset[nScanline], nScanline );
655 : }
656 : else
657 : {
658 0 : CPLDebug("BSB", "Seek to offset %d for scanline %d failed.",
659 0 : psInfo->panLineOffset[nScanline], nScanline );
660 : }
661 0 : return FALSE;
662 : }
663 :
664 : /* -------------------------------------------------------------------- */
665 : /* Read the line number. Pre 2.0 BSB seemed to expect the line */
666 : /* numbers to be zero based, while 2.0 and later seemed to */
667 : /* expect it to be one based, and for a 0 to be some sort of */
668 : /* missing line marker. */
669 : /* -------------------------------------------------------------------- */
670 : do {
671 301 : byNext = BSBGetc( psInfo, psInfo->bNO1, &bErrorFlag );
672 :
673 : /* Special hack to skip over extra zeros in some files, such
674 : ** as optech/sample1.kap.
675 : */
676 602 : while( nScanline != 0 && nLineMarker == 0 && byNext == 0 && !bErrorFlag )
677 0 : byNext = BSBGetc( psInfo, psInfo->bNO1, &bErrorFlag );
678 :
679 301 : nLineMarker = nLineMarker * 128 + (byNext & 0x7f);
680 301 : } while( (byNext & 0x80) != 0 );
681 :
682 301 : if ( bErrorFlag )
683 : {
684 1 : if (bVerboseIfError)
685 : {
686 1 : CPLError( CE_Failure, CPLE_FileIO,
687 : "Truncated BSB file or I/O error." );
688 : }
689 1 : return FALSE;
690 : }
691 :
692 600 : if( nLineMarker != nScanline
693 300 : && nLineMarker != nScanline + 1 )
694 : {
695 1 : if (bVerboseIfError)
696 : {
697 0 : CPLError( CE_Failure, CPLE_AppDefined,
698 : "Got scanline id %d when looking for %d @ offset %d.",
699 0 : nLineMarker, nScanline+1, psInfo->panLineOffset[nScanline]);
700 : }
701 : else
702 : {
703 1 : CPLDebug("BSB", "Got scanline id %d when looking for %d @ offset %d.",
704 1 : nLineMarker, nScanline+1, psInfo->panLineOffset[nScanline]);
705 : }
706 1 : return FALSE;
707 : }
708 :
709 299 : return TRUE;
710 : }
711 :
712 : /************************************************************************/
713 : /* BSBReadScanline() */
714 : /* @param nScanline zero based line number */
715 : /************************************************************************/
716 :
717 250 : int BSBReadScanline( BSBInfo *psInfo, int nScanline,
718 : unsigned char *pabyScanlineBuf )
719 :
720 : {
721 250 : int nValueShift, iPixel = 0;
722 : unsigned char byValueMask, byCountMask;
723 250 : FILE *fp = psInfo->fp;
724 : int byNext, i;
725 :
726 : /* -------------------------------------------------------------------- */
727 : /* Do we know where the requested line is? If not, read all */
728 : /* the preceeding ones to "find" our line. */
729 : /* -------------------------------------------------------------------- */
730 250 : if( nScanline < 0 || nScanline >= psInfo->nYSize )
731 : {
732 0 : CPLError( CE_Failure, CPLE_FileIO,
733 : "Scanline %d out of range.",
734 : nScanline );
735 0 : return FALSE;
736 : }
737 :
738 250 : if( psInfo->panLineOffset[nScanline] == -1 )
739 : {
740 0 : for( i = 0; i < nScanline; i++ )
741 : {
742 0 : if( psInfo->panLineOffset[i+1] == -1 )
743 : {
744 0 : if( !BSBReadScanline( psInfo, i, pabyScanlineBuf ) )
745 0 : return FALSE;
746 : }
747 : }
748 : }
749 :
750 : /* -------------------------------------------------------------------- */
751 : /* Seek to the beginning of the scanline and check that the */
752 : /* scanline number in file is consistant with what we expect */
753 : /* -------------------------------------------------------------------- */
754 250 : if ( !BSBSeekAndCheckScanlineNumber(psInfo, nScanline, TRUE) )
755 : {
756 1 : return FALSE;
757 : }
758 :
759 : /* -------------------------------------------------------------------- */
760 : /* Setup masking values. */
761 : /* -------------------------------------------------------------------- */
762 249 : nValueShift = 7 - psInfo->nColorSize;
763 249 : byValueMask = (unsigned char)
764 249 : ((((1 << psInfo->nColorSize)) - 1) << nValueShift);
765 249 : byCountMask = (unsigned char)
766 249 : (1 << (7 - psInfo->nColorSize)) - 1;
767 :
768 : /* -------------------------------------------------------------------- */
769 : /* Read and expand runs. */
770 : /* If for some reason the buffer is not filled, */
771 : /* just repeat the process until the buffer is filled. */
772 : /* This is the case for IS1612_4.NOS (#2782) */
773 : /* -------------------------------------------------------------------- */
774 : do
775 : {
776 250 : int bErrorFlag = FALSE;
777 25396 : while( (byNext = BSBGetc(psInfo,psInfo->bNO1, &bErrorFlag)) != 0 &&
778 12448 : !bErrorFlag)
779 : {
780 : int nPixValue;
781 : int nRunCount, i;
782 :
783 12448 : nPixValue = (byNext & byValueMask) >> nValueShift;
784 :
785 12448 : nRunCount = byNext & byCountMask;
786 :
787 24896 : while( (byNext & 0x80) != 0 && !bErrorFlag)
788 : {
789 0 : byNext = BSBGetc( psInfo, psInfo->bNO1, &bErrorFlag );
790 0 : nRunCount = nRunCount * 128 + (byNext & 0x7f);
791 : }
792 :
793 : /* Prevent over-run of line data */
794 12448 : if (nRunCount < 0 || nRunCount > psInfo->nXSize)
795 : {
796 0 : CPLError( CE_Failure, CPLE_FileIO,
797 : "Corrupted run count : %d", nRunCount );
798 0 : return FALSE;
799 : }
800 :
801 12448 : if( iPixel + nRunCount + 1 > psInfo->nXSize )
802 0 : nRunCount = psInfo->nXSize - iPixel - 1;
803 :
804 24896 : for( i = 0; i < nRunCount+1; i++ )
805 12448 : pabyScanlineBuf[iPixel++] = (unsigned char) nPixValue;
806 : }
807 250 : if ( bErrorFlag )
808 : {
809 1 : CPLError( CE_Failure, CPLE_FileIO,
810 : "Truncated BSB file or I/O error." );
811 1 : return FALSE;
812 : }
813 :
814 : /* -------------------------------------------------------------------- */
815 : /* For reasons that are unclear, some scanlines are exactly one */
816 : /* pixel short (such as in the BSB 3.0 354704.KAP product from */
817 : /* NDI/CHS) but are otherwise OK. Just add a zero if this */
818 : /* appear to have occured. */
819 : /* -------------------------------------------------------------------- */
820 249 : if( iPixel == psInfo->nXSize - 1 )
821 0 : pabyScanlineBuf[iPixel++] = 0;
822 :
823 : /* -------------------------------------------------------------------- */
824 : /* If we have not enough data and no offset table, check that the */
825 : /* next bytes are not the expected next scanline number. If they are */
826 : /* not, then we can use them to fill the row again */
827 : /* -------------------------------------------------------------------- */
828 251 : else if (iPixel < psInfo->nXSize &&
829 1 : nScanline != psInfo->nYSize-1 &&
830 1 : psInfo->panLineOffset[nScanline+1] == -1)
831 : {
832 1 : int nCurOffset = (int)(VSIFTellL( fp ) - psInfo->nBufferSize) +
833 1 : psInfo->nBufferOffset;
834 1 : psInfo->panLineOffset[nScanline+1] = nCurOffset;
835 1 : if (BSBSeekAndCheckScanlineNumber(psInfo, nScanline + 1, FALSE))
836 : {
837 0 : CPLDebug("BSB", "iPixel=%d, nScanline=%d, nCurOffset=%d --> found new row marker", iPixel, nScanline, nCurOffset);
838 0 : break;
839 : }
840 : else
841 : {
842 1 : CPLDebug("BSB", "iPixel=%d, nScanline=%d, nCurOffset=%d --> did NOT find new row marker", iPixel, nScanline, nCurOffset);
843 :
844 : /* The next bytes are not the expected next scanline number, so */
845 : /* use them to fill the row */
846 1 : VSIFSeekL( fp, nCurOffset, SEEK_SET );
847 1 : psInfo->panLineOffset[nScanline+1] = -1;
848 1 : psInfo->nBufferOffset = 0;
849 1 : psInfo->nBufferSize = 0;
850 : }
851 : }
852 : }
853 249 : while ( iPixel < psInfo->nXSize &&
854 1 : (nScanline == psInfo->nYSize-1 ||
855 1 : psInfo->panLineOffset[nScanline+1] == -1 ||
856 251 : VSIFTellL( fp ) - psInfo->nBufferSize + psInfo->nBufferOffset < psInfo->panLineOffset[nScanline+1]) );
857 :
858 : /* -------------------------------------------------------------------- */
859 : /* If the line buffer is not filled after reading the line in the */
860 : /* file upto the next line offset, just fill it with zeros. */
861 : /* (The last pixel value from nPixValue could be a better value?) */
862 : /* -------------------------------------------------------------------- */
863 496 : while( iPixel < psInfo->nXSize )
864 0 : pabyScanlineBuf[iPixel++] = 0;
865 :
866 : /* -------------------------------------------------------------------- */
867 : /* Remember the start of the next line. */
868 : /* But only if it is not already known. */
869 : /* -------------------------------------------------------------------- */
870 493 : if( nScanline < psInfo->nYSize-1 &&
871 245 : psInfo->panLineOffset[nScanline+1] == -1 )
872 : {
873 196 : psInfo->panLineOffset[nScanline+1] = (int)
874 : (VSIFTellL( fp ) - psInfo->nBufferSize) + psInfo->nBufferOffset;
875 : }
876 :
877 248 : return TRUE;
878 : }
879 :
880 : /************************************************************************/
881 : /* BSBClose() */
882 : /************************************************************************/
883 :
884 5 : void BSBClose( BSBInfo *psInfo )
885 :
886 : {
887 5 : if( psInfo->fp != NULL )
888 5 : VSIFCloseL( psInfo->fp );
889 :
890 5 : CPLFree( psInfo->pabyBuffer );
891 :
892 5 : CSLDestroy( psInfo->papszHeader );
893 5 : CPLFree( psInfo->panLineOffset );
894 5 : CPLFree( psInfo->pabyPCT );
895 5 : CPLFree( psInfo );
896 5 : }
897 :
898 : /************************************************************************/
899 : /* BSBCreate() */
900 : /************************************************************************/
901 :
902 0 : BSBInfo *BSBCreate( const char *pszFilename, int nCreationFlags, int nVersion,
903 : int nXSize, int nYSize )
904 :
905 : {
906 : FILE *fp;
907 : BSBInfo *psInfo;
908 :
909 : /* -------------------------------------------------------------------- */
910 : /* Open new KAP file. */
911 : /* -------------------------------------------------------------------- */
912 0 : fp = VSIFOpenL( pszFilename, "wb" );
913 0 : if( fp == NULL )
914 : {
915 0 : CPLError( CE_Failure, CPLE_OpenFailed,
916 : "Failed to open output file %s.",
917 : pszFilename );
918 0 : return NULL;
919 : }
920 :
921 : /* -------------------------------------------------------------------- */
922 : /* Write out BSB line. */
923 : /* -------------------------------------------------------------------- */
924 0 : VSIFPrintfL( fp,
925 : "!Copyright unknown\n" );
926 0 : VSIFPrintfL( fp,
927 : "VER/%.1f\n", nVersion / 100.0 );
928 0 : VSIFPrintfL( fp,
929 : "BSB/NA=UNKNOWN,NU=999502,RA=%d,%d,DU=254\n",
930 : nXSize, nYSize );
931 0 : VSIFPrintfL( fp,
932 : "KNP/SC=25000,GD=WGS84,PR=Mercator\n" );
933 0 : VSIFPrintfL( fp,
934 : " PP=31.500000,PI=0.033333,SP=,SK=0.000000,TA=90.000000\n");
935 0 : VSIFPrintfL( fp,
936 : " UN=Metres,SD=HHWLT,DX=2.500000,DY=2.500000\n");
937 :
938 :
939 : /* -------------------------------------------------------------------- */
940 : /* Create info structure. */
941 : /* -------------------------------------------------------------------- */
942 0 : psInfo = (BSBInfo *) CPLCalloc(1,sizeof(BSBInfo));
943 0 : psInfo->fp = fp;
944 0 : psInfo->bNO1 = FALSE;
945 0 : psInfo->nVersion = nVersion;
946 0 : psInfo->nXSize = nXSize;
947 0 : psInfo->nYSize = nYSize;
948 0 : psInfo->bNewFile = TRUE;
949 0 : psInfo->nLastLineWritten = -1;
950 :
951 0 : return psInfo;
952 : }
953 :
954 : /************************************************************************/
955 : /* BSBWritePCT() */
956 : /************************************************************************/
957 :
958 0 : int BSBWritePCT( BSBInfo *psInfo, int nPCTSize, unsigned char *pabyPCT )
959 :
960 : {
961 : int i;
962 :
963 : /* -------------------------------------------------------------------- */
964 : /* Verify the PCT not too large. */
965 : /* -------------------------------------------------------------------- */
966 0 : if( nPCTSize > 128 )
967 : {
968 0 : CPLError( CE_Failure, CPLE_AppDefined,
969 : "Pseudo-color table too large (%d entries), at most 128\n"
970 : " entries allowed in BSB format.", nPCTSize );
971 0 : return FALSE;
972 : }
973 :
974 : /* -------------------------------------------------------------------- */
975 : /* Compute the number of bits required for the colors. */
976 : /* -------------------------------------------------------------------- */
977 0 : for( psInfo->nColorSize = 1;
978 0 : (1 << psInfo->nColorSize) < nPCTSize;
979 0 : psInfo->nColorSize++ ) {}
980 :
981 : /* -------------------------------------------------------------------- */
982 : /* Write out the color table. Note that color table entry zero */
983 : /* is ignored. Zero is not a legal value. */
984 : /* -------------------------------------------------------------------- */
985 0 : for( i = 1; i < nPCTSize; i++ )
986 : {
987 0 : VSIFPrintfL( psInfo->fp,
988 : "RGB/%d,%d,%d,%d\n",
989 0 : i, pabyPCT[i*3+0], pabyPCT[i*3+1], pabyPCT[i*3+2] );
990 : }
991 :
992 0 : return TRUE;
993 : }
994 :
995 : /************************************************************************/
996 : /* BSBWriteScanline() */
997 : /************************************************************************/
998 :
999 0 : int BSBWriteScanline( BSBInfo *psInfo, unsigned char *pabyScanlineBuf )
1000 :
1001 : {
1002 : int nValue, iX;
1003 :
1004 0 : if( psInfo->nLastLineWritten == psInfo->nYSize - 1 )
1005 : {
1006 0 : CPLError( CE_Failure, CPLE_AppDefined,
1007 : "Attempt to write too many scanlines." );
1008 0 : return FALSE;
1009 : }
1010 :
1011 : /* -------------------------------------------------------------------- */
1012 : /* If this is the first scanline writen out the EOF marker, and */
1013 : /* the introductory info in the image segment. */
1014 : /* -------------------------------------------------------------------- */
1015 0 : if( psInfo->nLastLineWritten == -1 )
1016 : {
1017 0 : VSIFPutcL( 0x1A, psInfo->fp );
1018 0 : VSIFPutcL( 0x00, psInfo->fp );
1019 0 : VSIFPutcL( psInfo->nColorSize, psInfo->fp );
1020 : }
1021 :
1022 : /* -------------------------------------------------------------------- */
1023 : /* Write the line number. */
1024 : /* -------------------------------------------------------------------- */
1025 0 : nValue = ++psInfo->nLastLineWritten;
1026 :
1027 0 : if( psInfo->nVersion >= 200 )
1028 0 : nValue++;
1029 :
1030 0 : if( nValue >= 128*128 )
1031 0 : VSIFPutcL( 0x80 | ((nValue & (0x7f<<14)) >> 14), psInfo->fp );
1032 0 : if( nValue >= 128 )
1033 0 : VSIFPutcL( 0x80 | ((nValue & (0x7f<<7)) >> 7), psInfo->fp );
1034 0 : VSIFPutcL( nValue & 0x7f, psInfo->fp );
1035 :
1036 : /* -------------------------------------------------------------------- */
1037 : /* Write out each pixel as a separate byte. We don't try to */
1038 : /* actually capture the runs since that radical and futuristic */
1039 : /* concept is patented! */
1040 : /* -------------------------------------------------------------------- */
1041 0 : for( iX = 0; iX < psInfo->nXSize; iX++ )
1042 : {
1043 0 : VSIFPutcL( pabyScanlineBuf[iX] << (7-psInfo->nColorSize),
1044 : psInfo->fp );
1045 : }
1046 :
1047 0 : VSIFPutcL( 0x00, psInfo->fp );
1048 :
1049 0 : return TRUE;
1050 : }
|