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