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