/src/gdal/frmts/bsb/bsb_read.c
Line | Count | Source (jump to first uncovered line) |
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 | | static void BSBUngetc(BSBInfo *psInfo, int nCharacter) |
102 | | |
103 | 1.80M | { |
104 | 1.80M | CPLAssert(psInfo->nSavedCharacter2 == -1000); |
105 | 1.80M | psInfo->nSavedCharacter2 = psInfo->nSavedCharacter; |
106 | 1.80M | psInfo->nSavedCharacter = nCharacter; |
107 | 1.80M | } |
108 | | |
109 | | /************************************************************************/ |
110 | | /* BSBGetc() */ |
111 | | /************************************************************************/ |
112 | | |
113 | | static int BSBGetc(BSBInfo *psInfo, int bNO1, int *pbErrorFlag) |
114 | | |
115 | 12.9M | { |
116 | 12.9M | int nByte; |
117 | | |
118 | 12.9M | if (psInfo->nSavedCharacter != -1000) |
119 | 1.80M | { |
120 | 1.80M | nByte = psInfo->nSavedCharacter; |
121 | 1.80M | psInfo->nSavedCharacter = psInfo->nSavedCharacter2; |
122 | 1.80M | psInfo->nSavedCharacter2 = -1000; |
123 | 1.80M | return nByte; |
124 | 1.80M | } |
125 | | |
126 | 11.1M | if (psInfo->nBufferOffset >= psInfo->nBufferSize) |
127 | 17.7k | { |
128 | 17.7k | psInfo->nBufferOffset = 0; |
129 | 17.7k | psInfo->nBufferSize = (int)VSIFReadL( |
130 | 17.7k | psInfo->pabyBuffer, 1, psInfo->nBufferAllocation, psInfo->fp); |
131 | 17.7k | if (psInfo->nBufferSize <= 0) |
132 | 187 | { |
133 | 187 | if (pbErrorFlag) |
134 | 17 | *pbErrorFlag = TRUE; |
135 | 187 | return 0; |
136 | 187 | } |
137 | 17.7k | } |
138 | | |
139 | 11.1M | nByte = psInfo->pabyBuffer[psInfo->nBufferOffset++]; |
140 | | |
141 | 11.1M | if (bNO1) |
142 | 0 | { |
143 | 0 | nByte = nByte - 9; |
144 | 0 | if (nByte < 0) |
145 | 0 | nByte = nByte + 256; |
146 | 0 | } |
147 | | |
148 | 11.1M | return nByte; |
149 | 11.1M | } |
150 | | |
151 | | /************************************************************************/ |
152 | | /* BSBOpen() */ |
153 | | /* */ |
154 | | /* Read BSB header, and return information. */ |
155 | | /************************************************************************/ |
156 | | |
157 | | BSBInfo *BSBOpen(const char *pszFilename) |
158 | | |
159 | 747 | { |
160 | 747 | VSILFILE *fp; |
161 | 747 | char achTestBlock[1000]; |
162 | 747 | char szLine[1000]; |
163 | 747 | int i, bNO1 = FALSE; |
164 | 747 | BSBInfo *psInfo; |
165 | 747 | int nSkipped = 0; |
166 | 747 | const char *pszPalette; |
167 | 747 | int nOffsetFirstLine; |
168 | 747 | int bErrorFlag = FALSE; |
169 | | |
170 | | /* -------------------------------------------------------------------- */ |
171 | | /* Which palette do we want to use? */ |
172 | | /* -------------------------------------------------------------------- */ |
173 | 747 | pszPalette = CPLGetConfigOption("BSB_PALETTE", "RGB"); |
174 | | |
175 | | /* -------------------------------------------------------------------- */ |
176 | | /* Open the file. */ |
177 | | /* -------------------------------------------------------------------- */ |
178 | 747 | fp = VSIFOpenL(pszFilename, "rb"); |
179 | 747 | if (fp == NULL) |
180 | 0 | { |
181 | 0 | CPLError(CE_Failure, CPLE_OpenFailed, "File %s not found.", |
182 | 0 | pszFilename); |
183 | 0 | return NULL; |
184 | 0 | } |
185 | | |
186 | | /* -------------------------------------------------------------------- */ |
187 | | /* Read the first 1000 bytes, and verify that it contains the */ |
188 | | /* "BSB/" keyword" */ |
189 | | /* -------------------------------------------------------------------- */ |
190 | 747 | if (VSIFReadL(achTestBlock, 1, sizeof(achTestBlock), fp) != |
191 | 747 | sizeof(achTestBlock)) |
192 | 0 | { |
193 | 0 | VSIFCloseL(fp); |
194 | 0 | CPLError(CE_Failure, CPLE_FileIO, |
195 | 0 | "Could not read first %d bytes for header!", |
196 | 0 | (int)sizeof(achTestBlock)); |
197 | 0 | return NULL; |
198 | 0 | } |
199 | | |
200 | 428k | for (i = 0; (size_t)i < sizeof(achTestBlock) - 4; i++) |
201 | 428k | { |
202 | | /* Test for "BSB/" */ |
203 | 428k | if (achTestBlock[i + 0] == 'B' && achTestBlock[i + 1] == 'S' && |
204 | 428k | achTestBlock[i + 2] == 'B' && achTestBlock[i + 3] == '/') |
205 | 727 | break; |
206 | | |
207 | | /* Test for "NOS/" */ |
208 | 428k | if (achTestBlock[i + 0] == 'N' && achTestBlock[i + 1] == 'O' && |
209 | 428k | achTestBlock[i + 2] == 'S' && achTestBlock[i + 3] == '/') |
210 | 0 | break; |
211 | | |
212 | | /* Test for "NOS/" offset by 9 in ASCII for NO1 files */ |
213 | 428k | if (achTestBlock[i + 0] == 'W' && achTestBlock[i + 1] == 'X' && |
214 | 428k | achTestBlock[i + 2] == '\\' && achTestBlock[i + 3] == '8') |
215 | 0 | { |
216 | 0 | bNO1 = TRUE; |
217 | 0 | break; |
218 | 0 | } |
219 | 428k | } |
220 | | |
221 | 747 | if (i == sizeof(achTestBlock) - 4) |
222 | 20 | { |
223 | 20 | VSIFCloseL(fp); |
224 | 20 | CPLError(CE_Failure, CPLE_AppDefined, |
225 | 20 | "This does not appear to be a BSB file, no BSB/ header."); |
226 | 20 | return NULL; |
227 | 20 | } |
228 | | |
229 | | /* -------------------------------------------------------------------- */ |
230 | | /* Create info structure. */ |
231 | | /* -------------------------------------------------------------------- */ |
232 | 727 | psInfo = (BSBInfo *)CPLCalloc(1, sizeof(BSBInfo)); |
233 | 727 | psInfo->fp = fp; |
234 | 727 | psInfo->bNO1 = bNO1; |
235 | | |
236 | 727 | psInfo->nBufferAllocation = 1024; |
237 | 727 | psInfo->pabyBuffer = (GByte *)CPLMalloc(psInfo->nBufferAllocation); |
238 | 727 | psInfo->nBufferSize = 0; |
239 | 727 | psInfo->nBufferOffset = 0; |
240 | 727 | psInfo->nSavedCharacter = -1000; |
241 | 727 | psInfo->nSavedCharacter2 = -1000; |
242 | | |
243 | | /* -------------------------------------------------------------------- */ |
244 | | /* Rewind, and read line by line. */ |
245 | | /* -------------------------------------------------------------------- */ |
246 | 727 | VSIFSeekL(fp, 0, SEEK_SET); |
247 | | |
248 | 1.22M | while (BSBReadHeaderLine(psInfo, szLine, sizeof(szLine), bNO1)) |
249 | 1.22M | { |
250 | 1.22M | char **papszTokens = NULL; |
251 | 1.22M | int nCount = 0; |
252 | | |
253 | 1.22M | if (szLine[0] != '\0' && szLine[1] != '\0' && szLine[2] != '\0' && |
254 | 1.22M | szLine[3] == '/') |
255 | 28.0k | { |
256 | 28.0k | psInfo->papszHeader = CSLAddString(psInfo->papszHeader, szLine); |
257 | 28.0k | papszTokens = |
258 | 28.0k | CSLTokenizeStringComplex(szLine + 4, ",=", FALSE, FALSE); |
259 | 28.0k | nCount = CSLCount(papszTokens); |
260 | 28.0k | } |
261 | 1.22M | if (papszTokens == NULL) |
262 | 1.19M | continue; |
263 | | |
264 | 28.0k | if (STARTS_WITH_CI(szLine, "BSB/")) |
265 | 222 | { |
266 | 222 | int nRAIndex; |
267 | | |
268 | 222 | nRAIndex = CSLFindString(papszTokens, "RA"); |
269 | 222 | if (nRAIndex < 0 || nRAIndex + 2 >= nCount) |
270 | 4 | { |
271 | 4 | CSLDestroy(papszTokens); |
272 | 4 | CPLError(CE_Failure, CPLE_AppDefined, |
273 | 4 | "Failed to extract RA from BSB/ line."); |
274 | 4 | BSBClose(psInfo); |
275 | 4 | return NULL; |
276 | 4 | } |
277 | 218 | psInfo->nXSize = atoi(papszTokens[nRAIndex + 1]); |
278 | 218 | psInfo->nYSize = atoi(papszTokens[nRAIndex + 2]); |
279 | 218 | } |
280 | 27.8k | else if (STARTS_WITH_CI(szLine, "NOS/")) |
281 | 0 | { |
282 | 0 | int nRAIndex; |
283 | |
|
284 | 0 | nRAIndex = CSLFindString(papszTokens, "RA"); |
285 | 0 | if (nRAIndex < 0 || nRAIndex + 4 >= nCount) |
286 | 0 | { |
287 | 0 | CSLDestroy(papszTokens); |
288 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
289 | 0 | "Failed to extract RA from NOS/ line."); |
290 | 0 | BSBClose(psInfo); |
291 | 0 | return NULL; |
292 | 0 | } |
293 | 0 | psInfo->nXSize = atoi(papszTokens[nRAIndex + 3]); |
294 | 0 | psInfo->nYSize = atoi(papszTokens[nRAIndex + 4]); |
295 | 0 | } |
296 | 27.8k | else if (EQUALN(szLine, pszPalette, 3) && szLine[0] != '\0' && |
297 | 27.8k | szLine[1] != '\0' && szLine[2] != '\0' && szLine[3] == '/' && |
298 | 27.8k | nCount >= 4) |
299 | 2.01k | { |
300 | 2.01k | int iPCT = atoi(papszTokens[0]); |
301 | 2.01k | if (iPCT < 0 || iPCT > 128) |
302 | 0 | { |
303 | 0 | CSLDestroy(papszTokens); |
304 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
305 | 0 | "BSBOpen : Invalid color table index. Probably due to " |
306 | 0 | "corrupted BSB file (iPCT = %d).", |
307 | 0 | iPCT); |
308 | 0 | BSBClose(psInfo); |
309 | 0 | return NULL; |
310 | 0 | } |
311 | 2.01k | if (iPCT > psInfo->nPCTSize - 1) |
312 | 1.04k | { |
313 | 1.04k | unsigned char *pabyNewPCT = |
314 | 1.04k | (unsigned char *)VSI_REALLOC_VERBOSE(psInfo->pabyPCT, |
315 | 1.04k | (iPCT + 1) * 3); |
316 | 1.04k | if (pabyNewPCT == NULL) |
317 | 0 | { |
318 | 0 | CSLDestroy(papszTokens); |
319 | 0 | BSBClose(psInfo); |
320 | 0 | return NULL; |
321 | 0 | } |
322 | 1.04k | psInfo->pabyPCT = pabyNewPCT; |
323 | 1.04k | memset(psInfo->pabyPCT + psInfo->nPCTSize * 3, 0, |
324 | 1.04k | (iPCT + 1 - psInfo->nPCTSize) * 3); |
325 | 1.04k | psInfo->nPCTSize = iPCT + 1; |
326 | 1.04k | } |
327 | | |
328 | 2.01k | psInfo->pabyPCT[iPCT * 3 + 0] = (unsigned char)atoi(papszTokens[1]); |
329 | 2.01k | psInfo->pabyPCT[iPCT * 3 + 1] = (unsigned char)atoi(papszTokens[2]); |
330 | 2.01k | psInfo->pabyPCT[iPCT * 3 + 2] = (unsigned char)atoi(papszTokens[3]); |
331 | 2.01k | } |
332 | 25.8k | else if (STARTS_WITH_CI(szLine, "VER/") && nCount >= 1) |
333 | 715 | { |
334 | 715 | psInfo->nVersion = (int)(100 * CPLAtof(papszTokens[0]) + 0.5); |
335 | 715 | } |
336 | | |
337 | 28.0k | CSLDestroy(papszTokens); |
338 | 28.0k | } |
339 | | |
340 | | /* -------------------------------------------------------------------- */ |
341 | | /* Verify we found required keywords. */ |
342 | | /* -------------------------------------------------------------------- */ |
343 | 723 | if (psInfo->nXSize == 0 || psInfo->nPCTSize == 0) |
344 | 643 | { |
345 | 643 | BSBClose(psInfo); |
346 | 643 | CPLError(CE_Failure, CPLE_AppDefined, |
347 | 643 | "Failed to find required RGB/ or BSB/ keyword in header."); |
348 | | |
349 | 643 | return NULL; |
350 | 643 | } |
351 | | |
352 | 80 | if (psInfo->nXSize <= 0 || psInfo->nYSize <= 0) |
353 | 0 | { |
354 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
355 | 0 | "Wrong dimensions found in header : %d x %d.", psInfo->nXSize, |
356 | 0 | psInfo->nYSize); |
357 | 0 | BSBClose(psInfo); |
358 | 0 | return NULL; |
359 | 0 | } |
360 | | |
361 | 80 | if (psInfo->nVersion == 0) |
362 | 6 | { |
363 | 6 | CPLError(CE_Warning, CPLE_AppDefined, |
364 | 6 | "VER (version) keyword not found, assuming 2.0."); |
365 | 6 | psInfo->nVersion = 200; |
366 | 6 | } |
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 | 80 | { |
386 | 80 | int nChar = -1; |
387 | | |
388 | 1.72k | while (nSkipped < 100 && |
389 | 1.72k | (BSBGetc(psInfo, bNO1, &bErrorFlag) != 0x1A || |
390 | 1.71k | (nChar = BSBGetc(psInfo, bNO1, &bErrorFlag)) != 0x00) && |
391 | 1.72k | !bErrorFlag) |
392 | 1.64k | { |
393 | 1.64k | if (nChar == 0x1A) |
394 | 0 | { |
395 | 0 | BSBUngetc(psInfo, nChar); |
396 | 0 | nChar = -1; |
397 | 0 | } |
398 | 1.64k | nSkipped++; |
399 | 1.64k | } |
400 | | |
401 | 80 | if (bErrorFlag) |
402 | 7 | { |
403 | 7 | BSBClose(psInfo); |
404 | 7 | CPLError(CE_Failure, CPLE_FileIO, |
405 | 7 | "Truncated BSB file or I/O error."); |
406 | 7 | return NULL; |
407 | 7 | } |
408 | | |
409 | 73 | if (nSkipped == 100) |
410 | 12 | { |
411 | 12 | BSBClose(psInfo); |
412 | 12 | CPLError(CE_Failure, CPLE_AppDefined, |
413 | 12 | "Failed to find compressed data segment of BSB file."); |
414 | 12 | return NULL; |
415 | 12 | } |
416 | 73 | } |
417 | | |
418 | | /* -------------------------------------------------------------------- */ |
419 | | /* Read the number of bit size of color numbers. */ |
420 | | /* -------------------------------------------------------------------- */ |
421 | 61 | 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 | 61 | if (nSkipped > 0 && psInfo->nColorSize >= 0x31 && |
427 | 61 | psInfo->nColorSize <= 0x38) |
428 | 0 | psInfo->nColorSize -= 0x30; |
429 | | |
430 | 61 | if (!(psInfo->nColorSize > 0 && psInfo->nColorSize <= 7)) |
431 | 1 | { |
432 | 1 | CPLError(CE_Failure, CPLE_AppDefined, |
433 | 1 | "BSBOpen : Bad value for nColorSize (%d). Probably due to " |
434 | 1 | "corrupted BSB file", |
435 | 1 | psInfo->nColorSize); |
436 | 1 | BSBClose(psInfo); |
437 | 1 | return NULL; |
438 | 1 | } |
439 | | |
440 | | /* -------------------------------------------------------------------- */ |
441 | | /* Initialize memory for line offset list. */ |
442 | | /* -------------------------------------------------------------------- */ |
443 | 60 | if (psInfo->nYSize > 10000000) |
444 | 0 | { |
445 | 0 | vsi_l_offset nCurOffset = VSIFTellL(fp); |
446 | 0 | 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 | 0 | { |
451 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Truncated file"); |
452 | 0 | BSBClose(psInfo); |
453 | 0 | return NULL; |
454 | 0 | } |
455 | 0 | VSIFSeekL(fp, nCurOffset, SEEK_SET); |
456 | 0 | } |
457 | 60 | psInfo->panLineOffset = |
458 | 60 | (int *)VSI_MALLOC2_VERBOSE(sizeof(int), psInfo->nYSize); |
459 | 60 | if (psInfo->panLineOffset == NULL) |
460 | 0 | { |
461 | 0 | BSBClose(psInfo); |
462 | 0 | return NULL; |
463 | 0 | } |
464 | | |
465 | | /* This is the offset to the data of first line, if there is no index table |
466 | | */ |
467 | 60 | nOffsetFirstLine = |
468 | 60 | (int)(VSIFTellL(fp) - psInfo->nBufferSize) + psInfo->nBufferOffset; |
469 | | |
470 | | /* -------------------------------------------------------------------- */ |
471 | | /* Read the line offset list */ |
472 | | /* -------------------------------------------------------------------- */ |
473 | 60 | if (!CPLTestBoolean(CPLGetConfigOption("BSB_DISABLE_INDEX", "NO"))) |
474 | 60 | { |
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 | 60 | int nVal; |
479 | 60 | int listIsOK = 1; |
480 | 60 | int nOffsetIndexTable; |
481 | 60 | vsi_l_offset nFileLenLarge; |
482 | 60 | int nFileLen; |
483 | | |
484 | | /* Seek fp to point the last 4 byte integer which points |
485 | | * the offset of the first line */ |
486 | 60 | VSIFSeekL(fp, 0, SEEK_END); |
487 | 60 | nFileLenLarge = VSIFTellL(fp); |
488 | 60 | if (nFileLenLarge > INT_MAX) |
489 | 0 | { |
490 | | // Potentially the format could support up to 32 bit unsigned ? |
491 | 0 | BSBClose(psInfo); |
492 | 0 | return NULL; |
493 | 0 | } |
494 | 60 | nFileLen = (int)nFileLenLarge; |
495 | 60 | VSIFSeekL(fp, nFileLen - 4, SEEK_SET); |
496 | | |
497 | 60 | VSIFReadL(&nVal, 1, 4, fp); // last 4 bytes |
498 | 60 | CPL_MSBPTR32(&nVal); |
499 | 60 | 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 | 60 | if (nOffsetIndexTable <= 0 || psInfo->nYSize > INT_MAX / 4 || |
508 | 60 | 4 * psInfo->nYSize > INT_MAX - nOffsetIndexTable) |
509 | 1 | { |
510 | | /* int32 overflow */ |
511 | 1 | BSBClose(psInfo); |
512 | 1 | return NULL; |
513 | 1 | } |
514 | 59 | if (nOffsetIndexTable + 4 * (psInfo->nYSize - 1) == nFileLen - 4) |
515 | 0 | { |
516 | 0 | CPLDebug("BSB", "Index size is one row shorter than declared image " |
517 | 0 | "height. Correct this"); |
518 | 0 | psInfo->nYSize--; |
519 | 0 | } |
520 | | |
521 | 59 | if (nOffsetIndexTable <= nOffsetFirstLine || |
522 | 59 | nOffsetIndexTable + 4 * psInfo->nYSize > nFileLen - 4) |
523 | 52 | { |
524 | | /* The last 4 bytes are not the value of the offset to the index |
525 | | * table */ |
526 | 52 | } |
527 | 7 | else if (VSIFSeekL(fp, nOffsetIndexTable, SEEK_SET) != 0) |
528 | 0 | { |
529 | 0 | CPLError(CE_Failure, CPLE_FileIO, |
530 | 0 | "Seek to offset 0x%08x for first line offset failed.", |
531 | 0 | nOffsetIndexTable); |
532 | 0 | } |
533 | 7 | else |
534 | 7 | { |
535 | 7 | int nIndexSize = (nFileLen - 4 - nOffsetIndexTable) / 4; |
536 | 7 | if (nIndexSize != psInfo->nYSize) |
537 | 2 | { |
538 | 2 | CPLDebug("BSB", "Index size is %d. Expected %d", nIndexSize, |
539 | 2 | psInfo->nYSize); |
540 | 2 | } |
541 | | |
542 | 1.60k | for (i = 0; i < psInfo->nYSize; i++) |
543 | 1.59k | { |
544 | 1.59k | VSIFReadL(&nVal, 1, 4, fp); |
545 | 1.59k | CPL_MSBPTR32(&nVal); |
546 | 1.59k | psInfo->panLineOffset[i] = nVal; |
547 | 1.59k | } |
548 | | /* Simple checks for the integrity of the list */ |
549 | 559 | for (i = 0; i < psInfo->nYSize; i++) |
550 | 557 | { |
551 | 557 | if (psInfo->panLineOffset[i] < nOffsetFirstLine || |
552 | 557 | psInfo->panLineOffset[i] >= nOffsetIndexTable || |
553 | 557 | (i < psInfo->nYSize - 1 && |
554 | 555 | psInfo->panLineOffset[i] > psInfo->panLineOffset[i + 1]) || |
555 | 557 | !BSBSeekAndCheckScanlineNumber(psInfo, i, FALSE)) |
556 | 5 | { |
557 | 5 | CPLDebug("BSB", "Index table is invalid at index %d", i); |
558 | 5 | listIsOK = 0; |
559 | 5 | break; |
560 | 5 | } |
561 | 557 | } |
562 | 7 | if (listIsOK) |
563 | 2 | { |
564 | 2 | CPLDebug("BSB", "Index table is valid"); |
565 | 2 | return psInfo; |
566 | 2 | } |
567 | 7 | } |
568 | 59 | } |
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 | 57 | psInfo->panLineOffset[0] = nOffsetFirstLine; |
574 | 15.0k | for (i = 1; i < psInfo->nYSize; i++) |
575 | 14.9k | psInfo->panLineOffset[i] = -1; |
576 | | |
577 | 57 | return psInfo; |
578 | 60 | } |
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 | | static int BSBReadHeaderLine(BSBInfo *psInfo, char *pszLine, int nLineMaxLen, |
590 | | int bNO1) |
591 | | |
592 | 1.22M | { |
593 | 1.22M | char chNext; |
594 | 1.22M | int nLineLen = 0; |
595 | 1.22M | bool bGot1A = false; |
596 | 9.93M | while (!VSIFEofL(psInfo->fp) && nLineLen < nLineMaxLen - 1) |
597 | 9.93M | { |
598 | 9.93M | 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 | 9.93M | if (chNext == '\0') |
602 | 666 | { |
603 | 666 | BSBUngetc(psInfo, chNext); |
604 | 666 | if (bGot1A) |
605 | 56 | BSBUngetc(psInfo, 0x1A); |
606 | 666 | return FALSE; |
607 | 666 | } |
608 | 9.93M | bGot1A = false; |
609 | | |
610 | 9.93M | if (chNext == 0x1A) |
611 | 6.23k | { |
612 | 6.23k | bGot1A = true; |
613 | 6.23k | continue; |
614 | 6.23k | } |
615 | | |
616 | | /* each CR/LF (or LF/CR) as if just "CR" */ |
617 | 9.92M | if (chNext == 10 || chNext == 13) |
618 | 1.30M | { |
619 | 1.30M | char chLF; |
620 | | |
621 | 1.30M | chLF = (char)BSBGetc(psInfo, bNO1, NULL); |
622 | 1.30M | if (chLF != 10 && chLF != 13) |
623 | 494k | BSBUngetc(psInfo, chLF); |
624 | 1.30M | chNext = '\n'; |
625 | 1.30M | } |
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 | 9.92M | if (chNext == '\n') |
631 | 1.30M | { |
632 | 1.30M | char chTest; |
633 | | |
634 | 1.30M | chTest = (char)BSBGetc(psInfo, bNO1, NULL); |
635 | | /* Are we done? */ |
636 | 1.30M | if (chTest != ' ') |
637 | 1.22M | { |
638 | 1.22M | BSBUngetc(psInfo, chTest); |
639 | 1.22M | pszLine[nLineLen] = '\0'; |
640 | 1.22M | return TRUE; |
641 | 1.22M | } |
642 | | |
643 | | /* eat pending spaces */ |
644 | 242k | while (chTest == ' ') |
645 | 160k | chTest = (char)BSBGetc(psInfo, bNO1, NULL); |
646 | 82.1k | BSBUngetc(psInfo, chTest); |
647 | | |
648 | | /* insert comma in data stream */ |
649 | 82.1k | pszLine[nLineLen++] = ','; |
650 | 82.1k | } |
651 | 8.61M | else |
652 | 8.61M | { |
653 | 8.61M | pszLine[nLineLen++] = chNext; |
654 | 8.61M | } |
655 | 9.92M | } |
656 | | |
657 | 57 | return FALSE; |
658 | 1.22M | } |
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 | | static unsigned UpdateLineMarker(unsigned nLineMarker, int byNext) |
671 | 11.5k | { |
672 | 11.5k | return nLineMarker * 128U + (unsigned)(byNext & 0x7f); |
673 | 11.5k | } |
674 | | |
675 | | static int BSBSeekAndCheckScanlineNumber(BSBInfo *psInfo, unsigned nScanline, |
676 | | int bVerboseIfError) |
677 | 5.01k | { |
678 | 5.01k | unsigned nLineMarker = 0; |
679 | 5.01k | int byNext; |
680 | 5.01k | VSILFILE *fp = psInfo->fp; |
681 | 5.01k | int bErrorFlag = FALSE; |
682 | | |
683 | | /* -------------------------------------------------------------------- */ |
684 | | /* Seek to requested scanline. */ |
685 | | /* -------------------------------------------------------------------- */ |
686 | 5.01k | psInfo->nBufferSize = 0; |
687 | 5.01k | if (VSIFSeekL(fp, psInfo->panLineOffset[nScanline], SEEK_SET) != 0) |
688 | 0 | { |
689 | 0 | if (bVerboseIfError) |
690 | 0 | { |
691 | 0 | CPLError(CE_Failure, CPLE_FileIO, |
692 | 0 | "Seek to offset %d for scanline %d failed.", |
693 | 0 | psInfo->panLineOffset[nScanline], nScanline); |
694 | 0 | } |
695 | 0 | else |
696 | 0 | { |
697 | 0 | CPLDebug("BSB", "Seek to offset %d for scanline %d failed.", |
698 | 0 | psInfo->panLineOffset[nScanline], nScanline); |
699 | 0 | } |
700 | 0 | return FALSE; |
701 | 0 | } |
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 | 5.01k | do |
710 | 11.5k | { |
711 | 11.5k | 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 | 153k | while (nScanline != 0 && nLineMarker == 0 && byNext == 0 && !bErrorFlag) |
717 | 141k | byNext = BSBGetc(psInfo, psInfo->bNO1, &bErrorFlag); |
718 | | |
719 | 11.5k | nLineMarker = UpdateLineMarker(nLineMarker, byNext); |
720 | 11.5k | } while ((byNext & 0x80) != 0); |
721 | | |
722 | 5.01k | if (bErrorFlag) |
723 | 4 | { |
724 | 4 | if (bVerboseIfError) |
725 | 4 | { |
726 | 4 | CPLError(CE_Failure, CPLE_FileIO, |
727 | 4 | "Truncated BSB file or I/O error."); |
728 | 4 | } |
729 | 4 | return FALSE; |
730 | 4 | } |
731 | 5.00k | if (nLineMarker != nScanline && nLineMarker != nScanline + 1) |
732 | 1.49k | { |
733 | 1.49k | int bIgnoreLineNumbers = |
734 | 1.49k | CPLTestBoolean(CPLGetConfigOption("BSB_IGNORE_LINENUMBERS", "NO")); |
735 | | |
736 | 1.49k | if (bVerboseIfError && !bIgnoreLineNumbers) |
737 | 27 | { |
738 | 27 | CPLError(CE_Failure, CPLE_AppDefined, |
739 | 27 | "Got scanline id %u when looking for %u @ offset %d.\nSet " |
740 | 27 | "BSB_IGNORE_LINENUMBERS=TRUE configuration option to try " |
741 | 27 | "file anyways.", |
742 | 27 | nLineMarker, nScanline + 1, |
743 | 27 | psInfo->panLineOffset[nScanline]); |
744 | 27 | } |
745 | 1.47k | else |
746 | 1.47k | { |
747 | 1.47k | CPLDebug( |
748 | 1.47k | "BSB", "Got scanline id %u when looking for %u @ offset %d.", |
749 | 1.47k | nLineMarker, nScanline + 1, psInfo->panLineOffset[nScanline]); |
750 | 1.47k | } |
751 | | |
752 | 1.49k | if (!bIgnoreLineNumbers) |
753 | 1.49k | return FALSE; |
754 | 1.49k | } |
755 | | |
756 | 3.51k | return TRUE; |
757 | 5.00k | } |
758 | | |
759 | | /************************************************************************/ |
760 | | /* BSBReadScanline() */ |
761 | | /* @param nScanline zero based line number */ |
762 | | /************************************************************************/ |
763 | | |
764 | | int BSBReadScanline(BSBInfo *psInfo, int nScanline, |
765 | | unsigned char *pabyScanlineBuf) |
766 | | |
767 | 2.90k | { |
768 | 2.90k | int nValueShift, iPixel = 0; |
769 | 2.90k | unsigned char byValueMask, byCountMask; |
770 | 2.90k | VSILFILE *fp = psInfo->fp; |
771 | 2.90k | 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 | 2.90k | if (nScanline < 0 || nScanline >= psInfo->nYSize) |
778 | 0 | { |
779 | 0 | CPLError(CE_Failure, CPLE_FileIO, "Scanline %d out of range.", |
780 | 0 | nScanline); |
781 | 0 | return FALSE; |
782 | 0 | } |
783 | | |
784 | 2.90k | if (psInfo->panLineOffset[nScanline] == -1) |
785 | 0 | { |
786 | 0 | for (i = 0; i < nScanline; i++) |
787 | 0 | { |
788 | 0 | if (psInfo->panLineOffset[i + 1] == -1) |
789 | 0 | { |
790 | 0 | if (!BSBReadScanline(psInfo, i, pabyScanlineBuf)) |
791 | 0 | return FALSE; |
792 | 0 | } |
793 | 0 | } |
794 | 0 | } |
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 | 2.90k | if (!BSBSeekAndCheckScanlineNumber(psInfo, nScanline, TRUE)) |
801 | 31 | { |
802 | 31 | return FALSE; |
803 | 31 | } |
804 | | |
805 | | /* -------------------------------------------------------------------- */ |
806 | | /* Setup masking values. */ |
807 | | /* -------------------------------------------------------------------- */ |
808 | 2.87k | nValueShift = 7 - psInfo->nColorSize; |
809 | 2.87k | byValueMask = |
810 | 2.87k | (unsigned char)((((1 << psInfo->nColorSize)) - 1) << nValueShift); |
811 | 2.87k | 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 | 2.87k | do |
820 | 4.34k | { |
821 | 4.34k | int bErrorFlag = FALSE; |
822 | 45.5k | while ((byNext = BSBGetc(psInfo, psInfo->bNO1, &bErrorFlag)) != 0 && |
823 | 45.5k | !bErrorFlag) |
824 | 41.2k | { |
825 | 41.2k | int nPixValue; |
826 | 41.2k | int nRunCount; |
827 | | |
828 | 41.2k | nPixValue = (byNext & byValueMask) >> nValueShift; |
829 | | |
830 | 41.2k | nRunCount = byNext & byCountMask; |
831 | | |
832 | 51.3k | while ((byNext & 0x80) != 0 && !bErrorFlag) |
833 | 10.0k | { |
834 | 10.0k | byNext = BSBGetc(psInfo, psInfo->bNO1, &bErrorFlag); |
835 | 10.0k | if (nRunCount > (INT_MAX - (byNext & 0x7f)) / 128) |
836 | 13 | { |
837 | 13 | CPLError(CE_Failure, CPLE_FileIO, "Corrupted run count"); |
838 | 13 | return FALSE; |
839 | 13 | } |
840 | 10.0k | nRunCount = nRunCount * 128 + (byNext & 0x7f); |
841 | 10.0k | } |
842 | | |
843 | | /* Prevent over-run of line data */ |
844 | 41.2k | if (nRunCount < 0 || nRunCount > INT_MAX - (iPixel + 1)) |
845 | 0 | { |
846 | 0 | CPLError(CE_Failure, CPLE_FileIO, "Corrupted run count : %d", |
847 | 0 | nRunCount); |
848 | 0 | return FALSE; |
849 | 0 | } |
850 | 41.2k | if (nRunCount > psInfo->nXSize) |
851 | 50 | { |
852 | 50 | CPLDebugOnce("BSB", "Too big run count : %d", nRunCount); |
853 | 50 | } |
854 | | |
855 | 41.2k | if (iPixel + nRunCount + 1 > psInfo->nXSize) |
856 | 4.50k | nRunCount = psInfo->nXSize - iPixel - 1; |
857 | | |
858 | 1.72M | for (i = 0; i < nRunCount + 1; i++) |
859 | 1.68M | pabyScanlineBuf[iPixel++] = (unsigned char)nPixValue; |
860 | 41.2k | } |
861 | 4.33k | if (bErrorFlag) |
862 | 6 | { |
863 | 6 | CPLError(CE_Failure, CPLE_FileIO, |
864 | 6 | "Truncated BSB file or I/O error."); |
865 | 6 | return FALSE; |
866 | 6 | } |
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 | 4.32k | 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 | 4.32k | else if (iPixel < psInfo->nXSize && nScanline != psInfo->nYSize - 1 && |
888 | 4.32k | psInfo->panLineOffset[nScanline + 1] == -1) |
889 | 1.55k | { |
890 | 1.55k | int nCurOffset = (int)(VSIFTellL(fp) - psInfo->nBufferSize) + |
891 | 1.55k | psInfo->nBufferOffset; |
892 | 1.55k | psInfo->panLineOffset[nScanline + 1] = nCurOffset; |
893 | 1.55k | if (BSBSeekAndCheckScanlineNumber(psInfo, nScanline + 1, FALSE)) |
894 | 83 | { |
895 | 83 | CPLDebug("BSB", |
896 | 83 | "iPixel=%d, nScanline=%d, nCurOffset=%d --> found new " |
897 | 83 | "row marker", |
898 | 83 | iPixel, nScanline, nCurOffset); |
899 | 83 | break; |
900 | 83 | } |
901 | 1.46k | else |
902 | 1.46k | { |
903 | 1.46k | CPLDebug("BSB", |
904 | 1.46k | "iPixel=%d, nScanline=%d, nCurOffset=%d --> did NOT " |
905 | 1.46k | "find new row marker", |
906 | 1.46k | 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.46k | VSIFSeekL(fp, nCurOffset, SEEK_SET); |
912 | 1.46k | psInfo->panLineOffset[nScanline + 1] = -1; |
913 | 1.46k | psInfo->nBufferOffset = 0; |
914 | 1.46k | psInfo->nBufferSize = 0; |
915 | 1.46k | } |
916 | 1.55k | } |
917 | 4.32k | } while (iPixel < psInfo->nXSize && |
918 | 4.24k | (nScanline == psInfo->nYSize - 1 || |
919 | 1.46k | psInfo->panLineOffset[nScanline + 1] == -1 || |
920 | 1.46k | VSIFTellL(fp) - psInfo->nBufferSize + psInfo->nBufferOffset < |
921 | 0 | (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 | 358k | while (iPixel < psInfo->nXSize) |
929 | 355k | pabyScanlineBuf[iPixel++] = 0; |
930 | | |
931 | | /* -------------------------------------------------------------------- */ |
932 | | /* Remember the start of the next line. */ |
933 | | /* But only if it is not already known. */ |
934 | | /* -------------------------------------------------------------------- */ |
935 | 2.85k | if (nScanline < psInfo->nYSize - 1 && |
936 | 2.85k | psInfo->panLineOffset[nScanline + 1] == -1) |
937 | 2.23k | { |
938 | 2.23k | psInfo->panLineOffset[nScanline + 1] = |
939 | 2.23k | (int)(VSIFTellL(fp) - psInfo->nBufferSize) + psInfo->nBufferOffset; |
940 | 2.23k | } |
941 | | |
942 | 2.85k | return TRUE; |
943 | 2.87k | } |
944 | | |
945 | | /************************************************************************/ |
946 | | /* BSBClose() */ |
947 | | /************************************************************************/ |
948 | | |
949 | | void BSBClose(BSBInfo *psInfo) |
950 | | |
951 | 727 | { |
952 | 727 | if (psInfo->fp != NULL) |
953 | 727 | VSIFCloseL(psInfo->fp); |
954 | | |
955 | 727 | CPLFree(psInfo->pabyBuffer); |
956 | | |
957 | 727 | CSLDestroy(psInfo->papszHeader); |
958 | 727 | CPLFree(psInfo->panLineOffset); |
959 | 727 | CPLFree(psInfo->pabyPCT); |
960 | 727 | CPLFree(psInfo); |
961 | 727 | } |
962 | | |
963 | | /************************************************************************/ |
964 | | /* BSBCreate() */ |
965 | | /************************************************************************/ |
966 | | |
967 | | BSBInfo *BSBCreate(const char *pszFilename, CPL_UNUSED int nCreationFlags, |
968 | | int nVersion, int nXSize, int nYSize) |
969 | 0 | { |
970 | 0 | VSILFILE *fp; |
971 | 0 | BSBInfo *psInfo; |
972 | | |
973 | | /* -------------------------------------------------------------------- */ |
974 | | /* Open new KAP file. */ |
975 | | /* -------------------------------------------------------------------- */ |
976 | 0 | fp = VSIFOpenL(pszFilename, "wb"); |
977 | 0 | if (fp == NULL) |
978 | 0 | { |
979 | 0 | CPLError(CE_Failure, CPLE_OpenFailed, "Failed to open output file %s.", |
980 | 0 | pszFilename); |
981 | 0 | return NULL; |
982 | 0 | } |
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 | 0 | nYSize); |
991 | 0 | VSIFPrintfL(fp, "KNP/SC=25000,GD=WGS84,PR=Mercator\n"); |
992 | 0 | VSIFPrintfL(fp, |
993 | 0 | " 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 | 0 | } |
1010 | | |
1011 | | /************************************************************************/ |
1012 | | /* BSBWritePCT() */ |
1013 | | /************************************************************************/ |
1014 | | |
1015 | | int BSBWritePCT(BSBInfo *psInfo, int nPCTSize, unsigned char *pabyPCT) |
1016 | | |
1017 | 0 | { |
1018 | 0 | int i; |
1019 | | |
1020 | | /* -------------------------------------------------------------------- */ |
1021 | | /* Verify the PCT not too large. */ |
1022 | | /* -------------------------------------------------------------------- */ |
1023 | 0 | if (nPCTSize > 128) |
1024 | 0 | { |
1025 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1026 | 0 | "Pseudo-color table too large (%d entries), at most 128\n" |
1027 | 0 | " entries allowed in BSB format.", |
1028 | 0 | nPCTSize); |
1029 | 0 | return FALSE; |
1030 | 0 | } |
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 | 0 | { |
1038 | 0 | } |
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 | 0 | { |
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 | 0 | } |
1049 | |
|
1050 | 0 | return TRUE; |
1051 | 0 | } |
1052 | | |
1053 | | /************************************************************************/ |
1054 | | /* BSBWriteScanline() */ |
1055 | | /************************************************************************/ |
1056 | | |
1057 | | int BSBWriteScanline(BSBInfo *psInfo, unsigned char *pabyScanlineBuf) |
1058 | | |
1059 | 0 | { |
1060 | 0 | int nValue, iX; |
1061 | |
|
1062 | 0 | if (psInfo->nLastLineWritten == psInfo->nYSize - 1) |
1063 | 0 | { |
1064 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1065 | 0 | "Attempt to write too many scanlines."); |
1066 | 0 | return FALSE; |
1067 | 0 | } |
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 | 0 | { |
1075 | 0 | VSIFPutcL(0x1A, psInfo->fp); |
1076 | 0 | VSIFPutcL(0x00, psInfo->fp); |
1077 | 0 | VSIFPutcL(psInfo->nColorSize, psInfo->fp); |
1078 | 0 | } |
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 | 0 | { |
1101 | 0 | VSIFPutcL(pabyScanlineBuf[iX] << (7 - psInfo->nColorSize), psInfo->fp); |
1102 | 0 | } |
1103 | |
|
1104 | 0 | VSIFPutcL(0x00, psInfo->fp); |
1105 | |
|
1106 | 0 | return TRUE; |
1107 | 0 | } |