Coverage Report

Created: 2025-06-09 08:44

/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
}