Coverage Report

Created: 2026-02-14 09:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/bsb/bsb_read.c
Line
Count
Source
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.59M
{
104
1.59M
    CPLAssert(psInfo->nSavedCharacter2 == -1000);
105
1.59M
    psInfo->nSavedCharacter2 = psInfo->nSavedCharacter;
106
1.59M
    psInfo->nSavedCharacter = nCharacter;
107
1.59M
}
108
109
/************************************************************************/
110
/*                              BSBGetc()                               */
111
/************************************************************************/
112
113
static int BSBGetc(BSBInfo *psInfo, int bNO1, int *pbErrorFlag)
114
115
12.6M
{
116
12.6M
    int nByte;
117
118
12.6M
    if (psInfo->nSavedCharacter != -1000)
119
1.59M
    {
120
1.59M
        nByte = psInfo->nSavedCharacter;
121
1.59M
        psInfo->nSavedCharacter = psInfo->nSavedCharacter2;
122
1.59M
        psInfo->nSavedCharacter2 = -1000;
123
1.59M
        return nByte;
124
1.59M
    }
125
126
11.0M
    if (psInfo->nBufferOffset >= psInfo->nBufferSize)
127
15.9k
    {
128
15.9k
        psInfo->nBufferOffset = 0;
129
15.9k
        psInfo->nBufferSize = (int)VSIFReadL(
130
15.9k
            psInfo->pabyBuffer, 1, psInfo->nBufferAllocation, psInfo->fp);
131
15.9k
        if (psInfo->nBufferSize <= 0)
132
195
        {
133
195
            if (pbErrorFlag)
134
18
                *pbErrorFlag = TRUE;
135
195
            return 0;
136
195
        }
137
15.9k
    }
138
139
11.0M
    nByte = psInfo->pabyBuffer[psInfo->nBufferOffset++];
140
141
11.0M
    if (bNO1)
142
0
    {
143
0
        nByte = nByte - 9;
144
0
        if (nByte < 0)
145
0
            nByte = nByte + 256;
146
0
    }
147
148
11.0M
    return nByte;
149
11.0M
}
150
151
/************************************************************************/
152
/*                              BSBOpen()                               */
153
/*                                                                      */
154
/*      Read BSB header, and return information.                        */
155
/************************************************************************/
156
157
BSBInfo *BSBOpen(const char *pszFilename)
158
159
764
{
160
764
    VSILFILE *fp;
161
764
    char achTestBlock[1000];
162
764
    char szLine[1000];
163
764
    int i, bNO1 = FALSE;
164
764
    BSBInfo *psInfo;
165
764
    int nSkipped = 0;
166
764
    const char *pszPalette;
167
764
    int nOffsetFirstLine;
168
764
    int bErrorFlag = FALSE;
169
170
    /* -------------------------------------------------------------------- */
171
    /*      Which palette do we want to use?                                */
172
    /* -------------------------------------------------------------------- */
173
764
    pszPalette = CPLGetConfigOption("BSB_PALETTE", "RGB");
174
175
    /* -------------------------------------------------------------------- */
176
    /*      Open the file.                                                  */
177
    /* -------------------------------------------------------------------- */
178
764
    fp = VSIFOpenL(pszFilename, "rb");
179
764
    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
764
    if (VSIFReadL(achTestBlock, 1, sizeof(achTestBlock), fp) !=
191
764
        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
438k
    for (i = 0; (size_t)i < sizeof(achTestBlock) - 4; i++)
201
438k
    {
202
        /* Test for "BSB/" */
203
438k
        if (achTestBlock[i + 0] == 'B' && achTestBlock[i + 1] == 'S' &&
204
806
            achTestBlock[i + 2] == 'B' && achTestBlock[i + 3] == '/')
205
745
            break;
206
207
        /* Test for "NOS/" */
208
437k
        if (achTestBlock[i + 0] == 'N' && achTestBlock[i + 1] == 'O' &&
209
251
            achTestBlock[i + 2] == 'S' && achTestBlock[i + 3] == '/')
210
0
            break;
211
212
        /* Test for "NOS/" offset by 9 in ASCII for NO1 files */
213
437k
        if (achTestBlock[i + 0] == 'W' && achTestBlock[i + 1] == 'X' &&
214
77
            achTestBlock[i + 2] == '\\' && achTestBlock[i + 3] == '8')
215
0
        {
216
0
            bNO1 = TRUE;
217
0
            break;
218
0
        }
219
437k
    }
220
221
764
    if (i == sizeof(achTestBlock) - 4)
222
19
    {
223
19
        VSIFCloseL(fp);
224
19
        CPLError(CE_Failure, CPLE_AppDefined,
225
19
                 "This does not appear to be a BSB file, no BSB/ header.");
226
19
        return NULL;
227
19
    }
228
229
    /* -------------------------------------------------------------------- */
230
    /*      Create info structure.                                          */
231
    /* -------------------------------------------------------------------- */
232
745
    psInfo = (BSBInfo *)CPLCalloc(1, sizeof(BSBInfo));
233
745
    psInfo->fp = fp;
234
745
    psInfo->bNO1 = bNO1;
235
236
745
    psInfo->nBufferAllocation = 1024;
237
745
    psInfo->pabyBuffer = (GByte *)CPLMalloc(psInfo->nBufferAllocation);
238
745
    psInfo->nBufferSize = 0;
239
745
    psInfo->nBufferOffset = 0;
240
745
    psInfo->nSavedCharacter = -1000;
241
745
    psInfo->nSavedCharacter2 = -1000;
242
243
    /* -------------------------------------------------------------------- */
244
    /*      Rewind, and read line by line.                                  */
245
    /* -------------------------------------------------------------------- */
246
745
    VSIFSeekL(fp, 0, SEEK_SET);
247
248
1.06M
    while (BSBReadHeaderLine(psInfo, szLine, sizeof(szLine), bNO1))
249
1.06M
    {
250
1.06M
        char **papszTokens = NULL;
251
1.06M
        int nCount = 0;
252
253
1.06M
        if (szLine[0] != '\0' && szLine[1] != '\0' && szLine[2] != '\0' &&
254
440k
            szLine[3] == '/')
255
33.2k
        {
256
33.2k
            psInfo->papszHeader = CSLAddString(psInfo->papszHeader, szLine);
257
33.2k
            papszTokens =
258
33.2k
                CSLTokenizeStringComplex(szLine + 4, ",=", FALSE, FALSE);
259
33.2k
            nCount = CSLCount(papszTokens);
260
33.2k
        }
261
1.06M
        if (papszTokens == NULL)
262
1.03M
            continue;
263
264
33.2k
        if (STARTS_WITH_CI(szLine, "BSB/"))
265
504
        {
266
504
            int nRAIndex;
267
268
504
            nRAIndex = CSLFindString(papszTokens, "RA");
269
504
            if (nRAIndex < 0 || nRAIndex + 2 >= nCount)
270
18
            {
271
18
                CSLDestroy(papszTokens);
272
18
                CPLError(CE_Failure, CPLE_AppDefined,
273
18
                         "Failed to extract RA from BSB/ line.");
274
18
                BSBClose(psInfo);
275
18
                return NULL;
276
18
            }
277
486
            psInfo->nXSize = atoi(papszTokens[nRAIndex + 1]);
278
486
            psInfo->nYSize = atoi(papszTokens[nRAIndex + 2]);
279
486
        }
280
32.7k
        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
32.7k
        else if (EQUALN(szLine, pszPalette, 3) && szLine[0] != '\0' &&
297
6.67k
                 szLine[1] != '\0' && szLine[2] != '\0' && szLine[3] == '/' &&
298
6.67k
                 nCount >= 4)
299
6.48k
        {
300
6.48k
            int iPCT = atoi(papszTokens[0]);
301
6.48k
            if (iPCT < 0 || iPCT > 128)
302
1
            {
303
1
                CSLDestroy(papszTokens);
304
1
                CPLError(CE_Failure, CPLE_AppDefined,
305
1
                         "BSBOpen : Invalid color table index. Probably due to "
306
1
                         "corrupted BSB file (iPCT = %d).",
307
1
                         iPCT);
308
1
                BSBClose(psInfo);
309
1
                return NULL;
310
1
            }
311
6.48k
            if (iPCT > psInfo->nPCTSize - 1)
312
4.45k
            {
313
4.45k
                unsigned char *pabyNewPCT =
314
4.45k
                    (unsigned char *)VSI_REALLOC_VERBOSE(psInfo->pabyPCT,
315
4.45k
                                                         (iPCT + 1) * 3);
316
4.45k
                if (pabyNewPCT == NULL)
317
0
                {
318
0
                    CSLDestroy(papszTokens);
319
0
                    BSBClose(psInfo);
320
0
                    return NULL;
321
0
                }
322
4.45k
                psInfo->pabyPCT = pabyNewPCT;
323
4.45k
                memset(psInfo->pabyPCT + psInfo->nPCTSize * 3, 0,
324
4.45k
                       (iPCT + 1 - psInfo->nPCTSize) * 3);
325
4.45k
                psInfo->nPCTSize = iPCT + 1;
326
4.45k
            }
327
328
6.48k
            psInfo->pabyPCT[iPCT * 3 + 0] = (unsigned char)atoi(papszTokens[1]);
329
6.48k
            psInfo->pabyPCT[iPCT * 3 + 1] = (unsigned char)atoi(papszTokens[2]);
330
6.48k
            psInfo->pabyPCT[iPCT * 3 + 2] = (unsigned char)atoi(papszTokens[3]);
331
6.48k
        }
332
26.3k
        else if (STARTS_WITH_CI(szLine, "VER/") && nCount >= 1)
333
1.27k
        {
334
1.27k
            psInfo->nVersion = (int)(100 * CPLAtof(papszTokens[0]) + 0.5);
335
1.27k
        }
336
337
33.2k
        CSLDestroy(papszTokens);
338
33.2k
    }
339
340
    /* -------------------------------------------------------------------- */
341
    /*      Verify we found required keywords.                              */
342
    /* -------------------------------------------------------------------- */
343
726
    if (psInfo->nXSize == 0 || psInfo->nPCTSize == 0)
344
630
    {
345
630
        BSBClose(psInfo);
346
630
        CPLError(CE_Failure, CPLE_AppDefined,
347
630
                 "Failed to find required RGB/ or BSB/ keyword in header.");
348
349
630
        return NULL;
350
630
    }
351
352
96
    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
96
    if (psInfo->nVersion == 0)
362
5
    {
363
5
        CPLError(CE_Warning, CPLE_AppDefined,
364
5
                 "VER (version) keyword not found, assuming 2.0.");
365
5
        psInfo->nVersion = 200;
366
5
    }
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
96
    {
386
96
        int nChar = -1;
387
388
1.98k
        while (nSkipped < 100 &&
389
1.97k
               (BSBGetc(psInfo, bNO1, &bErrorFlag) != 0x1A ||
390
78
                (nChar = BSBGetc(psInfo, bNO1, &bErrorFlag)) != 0x00) &&
391
1.89k
               !bErrorFlag)
392
1.89k
        {
393
1.89k
            if (nChar == 0x1A)
394
0
            {
395
0
                BSBUngetc(psInfo, nChar);
396
0
                nChar = -1;
397
0
            }
398
1.89k
            nSkipped++;
399
1.89k
        }
400
401
96
        if (bErrorFlag)
402
6
        {
403
6
            BSBClose(psInfo);
404
6
            CPLError(CE_Failure, CPLE_FileIO,
405
6
                     "Truncated BSB file or I/O error.");
406
6
            return NULL;
407
6
        }
408
409
90
        if (nSkipped == 100)
410
16
        {
411
16
            BSBClose(psInfo);
412
16
            CPLError(CE_Failure, CPLE_AppDefined,
413
16
                     "Failed to find compressed data segment of BSB file.");
414
16
            return NULL;
415
16
        }
416
90
    }
417
418
    /* -------------------------------------------------------------------- */
419
    /*      Read the number of bit size of color numbers.                   */
420
    /* -------------------------------------------------------------------- */
421
74
    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
74
    if (nSkipped > 0 && psInfo->nColorSize >= 0x31 &&
427
0
        psInfo->nColorSize <= 0x38)
428
0
        psInfo->nColorSize -= 0x30;
429
430
74
    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
73
    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
73
    psInfo->panLineOffset =
458
73
        (int *)VSI_MALLOC2_VERBOSE(sizeof(int), psInfo->nYSize);
459
73
    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
73
    nOffsetFirstLine =
468
73
        (int)(VSIFTellL(fp) - psInfo->nBufferSize) + psInfo->nBufferOffset;
469
470
    /* -------------------------------------------------------------------- */
471
    /*       Read the line offset list                                      */
472
    /* -------------------------------------------------------------------- */
473
73
    if (!CPLTestBoolean(CPLGetConfigOption("BSB_DISABLE_INDEX", "NO")))
474
73
    {
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
73
        int nVal;
479
73
        int listIsOK = 1;
480
73
        int nOffsetIndexTable;
481
73
        vsi_l_offset nFileLenLarge;
482
73
        int nFileLen;
483
484
        /* Seek fp to point the last 4 byte integer which points
485
         * the offset of the first line */
486
73
        VSIFSeekL(fp, 0, SEEK_END);
487
73
        nFileLenLarge = VSIFTellL(fp);
488
73
        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
73
        nFileLen = (int)nFileLenLarge;
495
73
        VSIFSeekL(fp, nFileLen - 4, SEEK_SET);
496
497
73
        VSIFReadL(&nVal, 1, 4, fp);  // last 4 bytes
498
73
        CPL_MSBPTR32(&nVal);
499
73
        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
73
        if (nOffsetIndexTable <= 0 || psInfo->nYSize > INT_MAX / 4 ||
508
73
            4 * psInfo->nYSize > INT_MAX - nOffsetIndexTable)
509
0
        {
510
            /* int32 overflow */
511
0
            BSBClose(psInfo);
512
0
            return NULL;
513
0
        }
514
73
        if (nOffsetIndexTable + 4 * (psInfo->nYSize - 1) == nFileLen - 4)
515
1
        {
516
1
            CPLDebug("BSB", "Index size is one row shorter than declared image "
517
1
                            "height. Correct this");
518
1
            psInfo->nYSize--;
519
1
        }
520
521
73
        if (nOffsetIndexTable <= nOffsetFirstLine ||
522
71
            nOffsetIndexTable + 4 * psInfo->nYSize > nFileLen - 4)
523
66
        {
524
            /* The last 4 bytes are not the value of the offset to the index
525
             * table */
526
66
        }
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
2.07k
            for (i = 0; i < psInfo->nYSize; i++)
543
2.06k
            {
544
2.06k
                VSIFReadL(&nVal, 1, 4, fp);
545
2.06k
                CPL_MSBPTR32(&nVal);
546
2.06k
                psInfo->panLineOffset[i] = nVal;
547
2.06k
            }
548
            /* Simple checks for the integrity of the list */
549
548
            for (i = 0; i < psInfo->nYSize; i++)
550
546
            {
551
546
                if (psInfo->panLineOffset[i] < nOffsetFirstLine ||
552
545
                    psInfo->panLineOffset[i] >= nOffsetIndexTable ||
553
543
                    (i < psInfo->nYSize - 1 &&
554
541
                     psInfo->panLineOffset[i] > psInfo->panLineOffset[i + 1]) ||
555
543
                    !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
546
            }
562
7
            if (listIsOK)
563
2
            {
564
2
                CPLDebug("BSB", "Index table is valid");
565
2
                return psInfo;
566
2
            }
567
7
        }
568
73
    }
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
71
    psInfo->panLineOffset[0] = nOffsetFirstLine;
574
10.9k
    for (i = 1; i < psInfo->nYSize; i++)
575
10.8k
        psInfo->panLineOffset[i] = -1;
576
577
71
    return psInfo;
578
73
}
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.06M
{
593
1.06M
    char chNext;
594
1.06M
    int nLineLen = 0;
595
1.06M
    bool bGot1A = false;
596
10.1M
    while (!VSIFEofL(psInfo->fp) && nLineLen < nLineMaxLen - 1)
597
10.1M
    {
598
10.1M
        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
10.1M
        if (chNext == '\0')
602
671
        {
603
671
            BSBUngetc(psInfo, chNext);
604
671
            if (bGot1A)
605
76
                BSBUngetc(psInfo, 0x1A);
606
671
            return FALSE;
607
671
        }
608
10.1M
        bGot1A = false;
609
610
10.1M
        if (chNext == 0x1A)
611
8.78k
        {
612
8.78k
            bGot1A = true;
613
8.78k
            continue;
614
8.78k
        }
615
616
        /* each CR/LF (or LF/CR) as if just "CR" */
617
10.1M
        if (chNext == 10 || chNext == 13)
618
1.12M
        {
619
1.12M
            char chLF;
620
621
1.12M
            chLF = (char)BSBGetc(psInfo, bNO1, NULL);
622
1.12M
            if (chLF != 10 && chLF != 13)
623
468k
                BSBUngetc(psInfo, chLF);
624
1.12M
            chNext = '\n';
625
1.12M
        }
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
10.1M
        if (chNext == '\n')
631
1.12M
        {
632
1.12M
            char chTest;
633
634
1.12M
            chTest = (char)BSBGetc(psInfo, bNO1, NULL);
635
            /* Are we done? */
636
1.12M
            if (chTest != ' ')
637
1.06M
            {
638
1.06M
                BSBUngetc(psInfo, chTest);
639
1.06M
                pszLine[nLineLen] = '\0';
640
1.06M
                return TRUE;
641
1.06M
            }
642
643
            /* eat pending spaces */
644
209k
            while (chTest == ' ')
645
143k
                chTest = (char)BSBGetc(psInfo, bNO1, NULL);
646
66.1k
            BSBUngetc(psInfo, chTest);
647
648
            /* insert comma in data stream */
649
66.1k
            pszLine[nLineLen++] = ',';
650
66.1k
        }
651
8.98M
        else
652
8.98M
        {
653
8.98M
            pszLine[nLineLen++] = chNext;
654
8.98M
        }
655
10.1M
    }
656
657
55
    return FALSE;
658
1.06M
}
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
5.58k
{
672
5.58k
    return nLineMarker * 128U + (unsigned)(byNext & 0x7f);
673
5.58k
}
674
675
static int BSBSeekAndCheckScanlineNumber(BSBInfo *psInfo, unsigned nScanline,
676
                                         int bVerboseIfError)
677
3.84k
{
678
3.84k
    unsigned nLineMarker = 0;
679
3.84k
    int byNext;
680
3.84k
    VSILFILE *fp = psInfo->fp;
681
3.84k
    int bErrorFlag = FALSE;
682
683
    /* -------------------------------------------------------------------- */
684
    /*      Seek to requested scanline.                                     */
685
    /* -------------------------------------------------------------------- */
686
3.84k
    psInfo->nBufferSize = 0;
687
3.84k
    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
3.84k
    do
710
5.58k
    {
711
5.58k
        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
51.6k
        while (nScanline != 0 && nLineMarker == 0 && byNext == 0 && !bErrorFlag)
717
46.0k
            byNext = BSBGetc(psInfo, psInfo->bNO1, &bErrorFlag);
718
719
5.58k
        nLineMarker = UpdateLineMarker(nLineMarker, byNext);
720
5.58k
    } while ((byNext & 0x80) != 0);
721
722
3.84k
    if (bErrorFlag)
723
3
    {
724
3
        if (bVerboseIfError)
725
3
        {
726
3
            CPLError(CE_Failure, CPLE_FileIO,
727
3
                     "Truncated BSB file or I/O error.");
728
3
        }
729
3
        return FALSE;
730
3
    }
731
3.84k
    if (nLineMarker != nScanline && nLineMarker != nScanline + 1)
732
776
    {
733
776
        int bIgnoreLineNumbers =
734
776
            CPLTestBoolean(CPLGetConfigOption("BSB_IGNORE_LINENUMBERS", "NO"));
735
736
776
        if (bVerboseIfError && !bIgnoreLineNumbers)
737
35
        {
738
35
            CPLError(CE_Failure, CPLE_AppDefined,
739
35
                     "Got scanline id %u when looking for %u @ offset %d.\nSet "
740
35
                     "BSB_IGNORE_LINENUMBERS=TRUE configuration option to try "
741
35
                     "file anyways.",
742
35
                     nLineMarker, nScanline + 1,
743
35
                     psInfo->panLineOffset[nScanline]);
744
35
        }
745
741
        else
746
741
        {
747
741
            CPLDebug(
748
741
                "BSB", "Got scanline id %u when looking for %u @ offset %d.",
749
741
                nLineMarker, nScanline + 1, psInfo->panLineOffset[nScanline]);
750
741
        }
751
752
776
        if (!bIgnoreLineNumbers)
753
776
            return FALSE;
754
776
    }
755
756
3.07k
    return TRUE;
757
3.84k
}
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.53k
{
768
2.53k
    int nValueShift, iPixel = 0;
769
2.53k
    unsigned char byValueMask, byCountMask;
770
2.53k
    VSILFILE *fp = psInfo->fp;
771
2.53k
    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.53k
    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.53k
    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.53k
    if (!BSBSeekAndCheckScanlineNumber(psInfo, nScanline, TRUE))
801
38
    {
802
38
        return FALSE;
803
38
    }
804
805
    /* -------------------------------------------------------------------- */
806
    /*      Setup masking values.                                           */
807
    /* -------------------------------------------------------------------- */
808
2.49k
    nValueShift = 7 - psInfo->nColorSize;
809
2.49k
    byValueMask =
810
2.49k
        (unsigned char)((((1 << psInfo->nColorSize)) - 1) << nValueShift);
811
2.49k
    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.49k
    do
820
3.23k
    {
821
3.23k
        int bErrorFlag = FALSE;
822
75.0k
        while ((byNext = BSBGetc(psInfo, psInfo->bNO1, &bErrorFlag)) != 0 &&
823
71.8k
               !bErrorFlag)
824
71.8k
        {
825
71.8k
            int nPixValue;
826
71.8k
            int nRunCount;
827
828
71.8k
            nPixValue = (byNext & byValueMask) >> nValueShift;
829
830
71.8k
            nRunCount = byNext & byCountMask;
831
832
78.8k
            while ((byNext & 0x80) != 0 && !bErrorFlag)
833
7.02k
            {
834
7.02k
                byNext = BSBGetc(psInfo, psInfo->bNO1, &bErrorFlag);
835
7.02k
                if (nRunCount > (INT_MAX - (byNext & 0x7f)) / 128)
836
10
                {
837
10
                    CPLError(CE_Failure, CPLE_FileIO, "Corrupted run count");
838
10
                    return FALSE;
839
10
                }
840
7.01k
                nRunCount = nRunCount * 128 + (byNext & 0x7f);
841
7.01k
            }
842
843
            /* Prevent over-run of line data */
844
71.8k
            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
71.8k
            if (nRunCount > psInfo->nXSize)
851
137
            {
852
137
                CPLDebugOnce("BSB", "Too big run count : %d", nRunCount);
853
137
            }
854
855
71.8k
            if (iPixel + nRunCount + 1 > psInfo->nXSize)
856
13.6k
                nRunCount = psInfo->nXSize - iPixel - 1;
857
858
1.14M
            for (i = 0; i < nRunCount + 1; i++)
859
1.07M
                pabyScanlineBuf[iPixel++] = (unsigned char)nPixValue;
860
71.8k
        }
861
3.22k
        if (bErrorFlag)
862
9
        {
863
9
            CPLError(CE_Failure, CPLE_FileIO,
864
9
                     "Truncated BSB file or I/O error.");
865
9
            return FALSE;
866
9
        }
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
3.22k
        if (iPixel == psInfo->nXSize - 1)
877
7
            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
3.21k
        else if (iPixel < psInfo->nXSize && nScanline != psInfo->nYSize - 1 &&
888
769
                 psInfo->panLineOffset[nScanline + 1] == -1)
889
769
        {
890
769
            int nCurOffset = (int)(VSIFTellL(fp) - psInfo->nBufferSize) +
891
769
                             psInfo->nBufferOffset;
892
769
            psInfo->panLineOffset[nScanline + 1] = nCurOffset;
893
769
            if (BSBSeekAndCheckScanlineNumber(psInfo, nScanline + 1, FALSE))
894
30
            {
895
30
                CPLDebug("BSB",
896
30
                         "iPixel=%d, nScanline=%d, nCurOffset=%d --> found new "
897
30
                         "row marker",
898
30
                         iPixel, nScanline, nCurOffset);
899
30
                break;
900
30
            }
901
739
            else
902
739
            {
903
739
                CPLDebug("BSB",
904
739
                         "iPixel=%d, nScanline=%d, nCurOffset=%d --> did NOT "
905
739
                         "find new row marker",
906
739
                         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
739
                VSIFSeekL(fp, nCurOffset, SEEK_SET);
912
739
                psInfo->panLineOffset[nScanline + 1] = -1;
913
739
                psInfo->nBufferOffset = 0;
914
739
                psInfo->nBufferSize = 0;
915
739
            }
916
769
        }
917
3.22k
    } while (iPixel < psInfo->nXSize &&
918
740
             (nScanline == psInfo->nYSize - 1 ||
919
739
              psInfo->panLineOffset[nScanline + 1] == -1 ||
920
0
              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
5.47k
    while (iPixel < psInfo->nXSize)
929
2.99k
        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.48k
    if (nScanline < psInfo->nYSize - 1 &&
936
2.46k
        psInfo->panLineOffset[nScanline + 1] == -1)
937
1.90k
    {
938
1.90k
        psInfo->panLineOffset[nScanline + 1] =
939
1.90k
            (int)(VSIFTellL(fp) - psInfo->nBufferSize) + psInfo->nBufferOffset;
940
1.90k
    }
941
942
2.48k
    return TRUE;
943
2.49k
}
944
945
/************************************************************************/
946
/*                              BSBClose()                              */
947
/************************************************************************/
948
949
void BSBClose(BSBInfo *psInfo)
950
951
745
{
952
745
    if (psInfo->fp != NULL)
953
745
        VSIFCloseL(psInfo->fp);
954
955
745
    CPLFree(psInfo->pabyBuffer);
956
957
745
    CSLDestroy(psInfo->papszHeader);
958
745
    CPLFree(psInfo->panLineOffset);
959
745
    CPLFree(psInfo->pabyPCT);
960
745
    CPLFree(psInfo);
961
745
}
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
}