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