Coverage Report

Created: 2026-02-14 06:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/giflib-code/quantize.c
Line
Count
Source
1
/*****************************************************************************
2
3
 quantize.c - quantize a high resolution image into lower one
4
5
 Based on: "Color Image Quantization for frame buffer Display", by
6
 Paul Heckbert SIGGRAPH 1982 page 297-307.
7
8
 This doesn't really belong in the core library, was undocumented,
9
 and was removed in 4.2.  Then it turned out some client apps were
10
 actually using it, so it was restored in 5.0.
11
12
******************************************************************************/
13
// SPDX-License-Identifier: MIT
14
// SPDX-FileCopyrightText: Copyright (C) Eric S. Raymond <esr@thyrsus.com>
15
16
#include <stdio.h>
17
#include <stdlib.h>
18
19
#include "gif_lib.h"
20
#include "gif_lib_private.h"
21
22
23.8M
#define ABS(x) ((x) > 0 ? (x) : (-(x)))
23
24
23.7M
#define COLOR_ARRAY_SIZE 32768
25
103M
#define BITS_PER_PRIM_COLOR 5
26
23.7M
#define MAX_PRIM_COLOR 0x1f
27
28
static int SortRGBAxis;
29
30
typedef struct QuantizedColorType {
31
  GifByteType RGB[3];
32
  GifByteType NewColorIndex;
33
  long Count;
34
  struct QuantizedColorType *Pnext;
35
} QuantizedColorType;
36
37
typedef struct NewColorMapType {
38
  GifByteType RGBMin[3], RGBWidth[3];
39
  unsigned int
40
      NumEntries;      /* # of QuantizedColorType in linked list below */
41
  unsigned long Count; /* Total number of pixels in all the entries */
42
  QuantizedColorType *QuantizedColors;
43
} NewColorMapType;
44
45
static int SubdivColorMap(NewColorMapType *NewColorSubdiv,
46
                          unsigned int ColorMapSize,
47
                          unsigned int *NewColorMapSize);
48
static int SortCmpRtn(const void *Entry1, const void *Entry2);
49
50
/******************************************************************************
51
 Quantize high resolution image into lower one. Input image consists of a
52
 2D array for each of the RGB colors with size Width by Height. There is no
53
 Color map for the input. Output is a quantized image with 2D array of
54
 indexes into the output color map.
55
   Note input image can be 24 bits at the most (8 for red/green/blue) and
56
 the output has 256 colors at the most (256 entries in the color map.).
57
 ColorMapSize specifies size of color map up to 256 and will be updated to
58
 real size before returning.
59
   Also non of the parameter are allocated by this routine.
60
   This function returns GIF_OK if successful, GIF_ERROR otherwise.
61
******************************************************************************/
62
int GifQuantizeBuffer(unsigned int Width, unsigned int Height,
63
                      int *ColorMapSize, const GifByteType *RedInput,
64
                      const GifByteType *GreenInput,
65
                      const GifByteType *BlueInput, GifByteType *OutputBuffer,
66
363
                      GifColorType *OutputColorMap) {
67
68
363
  unsigned int Index, NumOfEntries;
69
363
  int i, j, MaxRGBError[3];
70
363
  unsigned int NewColorMapSize;
71
363
  long Red, Green, Blue;
72
363
  NewColorMapType NewColorSubdiv[256];
73
363
  QuantizedColorType *ColorArrayEntries, *QuantizedColor;
74
75
363
  ColorArrayEntries = (QuantizedColorType *)malloc(
76
363
      sizeof(QuantizedColorType) * COLOR_ARRAY_SIZE);
77
363
  if (ColorArrayEntries == NULL) {
78
0
    return GIF_ERROR;
79
0
  }
80
81
11.8M
  for (i = 0; i < COLOR_ARRAY_SIZE; i++) {
82
11.8M
    ColorArrayEntries[i].RGB[0] = i >> (2 * BITS_PER_PRIM_COLOR);
83
11.8M
    ColorArrayEntries[i].RGB[1] =
84
11.8M
        (i >> BITS_PER_PRIM_COLOR) & MAX_PRIM_COLOR;
85
11.8M
    ColorArrayEntries[i].RGB[2] = i & MAX_PRIM_COLOR;
86
11.8M
    ColorArrayEntries[i].Count = 0;
87
11.8M
  }
88
89
  /* Sample the colors and their distribution: */
90
7.94M
  for (i = 0; i < (int)(Width * Height); i++) {
91
7.94M
    Index = ((RedInput[i] >> (8 - BITS_PER_PRIM_COLOR))
92
7.94M
             << (2 * BITS_PER_PRIM_COLOR)) +
93
7.94M
            ((GreenInput[i] >> (8 - BITS_PER_PRIM_COLOR))
94
7.94M
             << BITS_PER_PRIM_COLOR) +
95
7.94M
            (BlueInput[i] >> (8 - BITS_PER_PRIM_COLOR));
96
7.94M
    ColorArrayEntries[Index].Count++;
97
7.94M
  }
98
99
  /* Put all the colors in the first entry of the color map, and call the
100
   * recursive subdivision process.  */
101
93.2k
  for (i = 0; i < 256; i++) {
102
92.9k
    NewColorSubdiv[i].QuantizedColors = NULL;
103
92.9k
    NewColorSubdiv[i].Count = NewColorSubdiv[i].NumEntries = 0;
104
371k
    for (j = 0; j < 3; j++) {
105
278k
      NewColorSubdiv[i].RGBMin[j] = 0;
106
278k
      NewColorSubdiv[i].RGBWidth[j] = 255;
107
278k
    }
108
92.9k
  }
109
110
  /* Find the non empty entries in the color table and chain them: */
111
942k
  for (i = 0; i < COLOR_ARRAY_SIZE; i++) {
112
942k
    if (ColorArrayEntries[i].Count > 0) {
113
363
      break;
114
363
    }
115
942k
  }
116
363
  QuantizedColor = NewColorSubdiv[0].QuantizedColors =
117
363
      &ColorArrayEntries[i];
118
363
  NumOfEntries = 1;
119
10.9M
  while (++i < COLOR_ARRAY_SIZE) {
120
10.9M
    if (ColorArrayEntries[i].Count > 0) {
121
1.58M
      QuantizedColor->Pnext = &ColorArrayEntries[i];
122
1.58M
      QuantizedColor = &ColorArrayEntries[i];
123
1.58M
      NumOfEntries++;
124
1.58M
    }
125
10.9M
  }
126
363
  QuantizedColor->Pnext = NULL;
127
128
363
  NewColorSubdiv[0].NumEntries =
129
363
      NumOfEntries; /* Different sampled colors */
130
363
  NewColorSubdiv[0].Count = ((long)Width) * Height; /* Pixels */
131
363
  NewColorMapSize = 1;
132
363
  if (SubdivColorMap(NewColorSubdiv, *ColorMapSize, &NewColorMapSize) !=
133
363
      GIF_OK) {
134
0
    free((char *)ColorArrayEntries);
135
0
    return GIF_ERROR;
136
0
  }
137
363
  if (NewColorMapSize < *ColorMapSize) {
138
    /* And clear rest of color map: */
139
32.8k
    for (i = NewColorMapSize; i < *ColorMapSize; i++) {
140
32.6k
      OutputColorMap[i].Red = OutputColorMap[i].Green =
141
32.6k
          OutputColorMap[i].Blue = 0;
142
32.6k
    }
143
173
  }
144
145
  /* Average the colors in each entry to be the color to be used in the
146
   * output color map, and plug it into the output color map itself. */
147
60.6k
  for (i = 0; i < NewColorMapSize; i++) {
148
60.2k
    if ((j = NewColorSubdiv[i].NumEntries) > 0) {
149
60.2k
      QuantizedColor = NewColorSubdiv[i].QuantizedColors;
150
60.2k
      Red = Green = Blue = 0;
151
1.64M
      while (QuantizedColor) {
152
1.58M
        QuantizedColor->NewColorIndex = i;
153
1.58M
        Red += QuantizedColor->RGB[0];
154
1.58M
        Green += QuantizedColor->RGB[1];
155
1.58M
        Blue += QuantizedColor->RGB[2];
156
1.58M
        QuantizedColor = QuantizedColor->Pnext;
157
1.58M
      }
158
60.2k
      OutputColorMap[i].Red =
159
60.2k
          (Red << (8 - BITS_PER_PRIM_COLOR)) / j;
160
60.2k
      OutputColorMap[i].Green =
161
60.2k
          (Green << (8 - BITS_PER_PRIM_COLOR)) / j;
162
60.2k
      OutputColorMap[i].Blue =
163
60.2k
          (Blue << (8 - BITS_PER_PRIM_COLOR)) / j;
164
60.2k
    }
165
60.2k
  }
166
167
  /* Finally scan the input buffer again and put the mapped index in the
168
   * output buffer.  */
169
363
  MaxRGBError[0] = MaxRGBError[1] = MaxRGBError[2] = 0;
170
7.94M
  for (i = 0; i < (int)(Width * Height); i++) {
171
7.94M
    Index = ((RedInput[i] >> (8 - BITS_PER_PRIM_COLOR))
172
7.94M
             << (2 * BITS_PER_PRIM_COLOR)) +
173
7.94M
            ((GreenInput[i] >> (8 - BITS_PER_PRIM_COLOR))
174
7.94M
             << BITS_PER_PRIM_COLOR) +
175
7.94M
            (BlueInput[i] >> (8 - BITS_PER_PRIM_COLOR));
176
7.94M
    Index = ColorArrayEntries[Index].NewColorIndex;
177
7.94M
    OutputBuffer[i] = Index;
178
7.94M
    if (MaxRGBError[0] <
179
7.94M
        ABS(OutputColorMap[Index].Red - RedInput[i])) {
180
1.67k
      MaxRGBError[0] =
181
1.67k
          ABS(OutputColorMap[Index].Red - RedInput[i]);
182
1.67k
    }
183
7.94M
    if (MaxRGBError[1] <
184
7.94M
        ABS(OutputColorMap[Index].Green - GreenInput[i])) {
185
1.79k
      MaxRGBError[1] =
186
1.79k
          ABS(OutputColorMap[Index].Green - GreenInput[i]);
187
1.79k
    }
188
7.94M
    if (MaxRGBError[2] <
189
7.94M
        ABS(OutputColorMap[Index].Blue - BlueInput[i])) {
190
1.83k
      MaxRGBError[2] =
191
1.83k
          ABS(OutputColorMap[Index].Blue - BlueInput[i]);
192
1.83k
    }
193
7.94M
  }
194
195
#ifdef DEBUG
196
  fprintf(stderr,
197
          "Quantization L(0) errors: Red = %d, Green = %d, Blue = %d.\n",
198
          MaxRGBError[0], MaxRGBError[1], MaxRGBError[2]);
199
#endif /* DEBUG */
200
201
363
  free((char *)ColorArrayEntries);
202
203
363
  *ColorMapSize = NewColorMapSize;
204
205
363
  return GIF_OK;
206
363
}
207
208
/******************************************************************************
209
 Routine to subdivide the RGB space recursively using median cut in each
210
 axes alternatingly until ColorMapSize different cubes exists.
211
 The biggest cube in one dimension is subdivide unless it has only one entry.
212
 Returns GIF_ERROR if failed, otherwise GIF_OK.
213
*******************************************************************************/
214
static int SubdivColorMap(NewColorMapType *NewColorSubdiv,
215
                          unsigned int ColorMapSize,
216
363
                          unsigned int *NewColorMapSize) {
217
218
363
  unsigned int i, j, Index = 0;
219
363
  QuantizedColorType *QuantizedColor, **SortArray;
220
221
60.2k
  while (ColorMapSize > *NewColorMapSize) {
222
    /* Find candidate for subdivision: */
223
60.0k
    long Sum, Count;
224
60.0k
    int MaxSize = -1;
225
60.0k
    unsigned int NumEntries, MinColor, MaxColor;
226
6.97M
    for (i = 0; i < *NewColorMapSize; i++) {
227
27.6M
      for (j = 0; j < 3; j++) {
228
20.7M
        if ((((int)NewColorSubdiv[i].RGBWidth[j]) >
229
20.7M
             MaxSize) &&
230
1.76M
            (NewColorSubdiv[i].NumEntries > 1)) {
231
221k
          MaxSize = NewColorSubdiv[i].RGBWidth[j];
232
221k
          Index = i;
233
221k
          SortRGBAxis = j;
234
221k
        }
235
20.7M
      }
236
6.91M
    }
237
238
60.0k
    if (MaxSize == -1) {
239
173
      return GIF_OK;
240
173
    }
241
242
    /* Split the entry Index into two along the axis SortRGBAxis: */
243
244
    /* Sort all elements in that entry along the given axis and
245
     * split at the median.  */
246
59.8k
    SortArray = (QuantizedColorType **)malloc(
247
59.8k
        sizeof(QuantizedColorType *) *
248
59.8k
        NewColorSubdiv[Index].NumEntries);
249
59.8k
    if (SortArray == NULL) {
250
0
      return GIF_ERROR;
251
0
    }
252
59.8k
    for (j = 0,
253
59.8k
        QuantizedColor = NewColorSubdiv[Index].QuantizedColors;
254
26.5M
         j < NewColorSubdiv[Index].NumEntries &&
255
26.4M
         QuantizedColor != NULL;
256
26.4M
         j++, QuantizedColor = QuantizedColor->Pnext) {
257
26.4M
      SortArray[j] = QuantizedColor;
258
26.4M
    }
259
260
    /*
261
     * Because qsort isn't stable, this can produce differing
262
     * results for the order of tuples depending on platform
263
     * details of how qsort() is implemented.
264
     *
265
     * We mitigate this problem by sorting on all three axes rather
266
     * than only the one specied by SortRGBAxis; that way the
267
     * instability can only become an issue if there are multiple
268
     * color indices referring to identical RGB tuples.  Older
269
     * versions of this sorted on only the one axis.
270
     */
271
59.8k
    qsort(SortArray, NewColorSubdiv[Index].NumEntries,
272
59.8k
          sizeof(QuantizedColorType *), SortCmpRtn);
273
274
    /* Relink the sorted list into one: */
275
26.4M
    for (j = 0; j < NewColorSubdiv[Index].NumEntries - 1; j++) {
276
26.4M
      SortArray[j]->Pnext = SortArray[j + 1];
277
26.4M
    }
278
59.8k
    SortArray[NewColorSubdiv[Index].NumEntries - 1]->Pnext = NULL;
279
59.8k
    NewColorSubdiv[Index].QuantizedColors = QuantizedColor =
280
59.8k
        SortArray[0];
281
59.8k
    free((char *)SortArray);
282
283
    /* Now simply add the Counts until we have half of the Count: */
284
59.8k
    Sum = NewColorSubdiv[Index].Count / 2 - QuantizedColor->Count;
285
59.8k
    NumEntries = 1;
286
59.8k
    Count = QuantizedColor->Count;
287
5.79M
    while (QuantizedColor->Pnext != NULL &&
288
5.79M
           (Sum -= QuantizedColor->Pnext->Count) >= 0 &&
289
5.73M
           QuantizedColor->Pnext->Pnext != NULL) {
290
5.73M
      QuantizedColor = QuantizedColor->Pnext;
291
5.73M
      NumEntries++;
292
5.73M
      Count += QuantizedColor->Count;
293
5.73M
    }
294
    /* Save the values of the last color of the first half, and
295
     * first of the second half so we can update the Bounding Boxes
296
     * later. Also as the colors are quantized and the BBoxes are
297
     * full 0..255, they need to be rescaled.
298
     */
299
59.8k
    MaxColor =
300
59.8k
        QuantizedColor->RGB[SortRGBAxis]; /* Max. of first half */
301
    /* coverity[var_deref_op] */
302
59.8k
    MinColor =
303
        // cppcheck-suppress nullPointerRedundantCheck
304
59.8k
        QuantizedColor->Pnext->RGB[SortRGBAxis]; /* of second */
305
59.8k
    MaxColor <<= (8 - BITS_PER_PRIM_COLOR);
306
59.8k
    MinColor <<= (8 - BITS_PER_PRIM_COLOR);
307
308
    /* Partition right here: */
309
59.8k
    NewColorSubdiv[*NewColorMapSize].QuantizedColors =
310
59.8k
        QuantizedColor->Pnext;
311
59.8k
    QuantizedColor->Pnext = NULL;
312
59.8k
    NewColorSubdiv[*NewColorMapSize].Count = Count;
313
59.8k
    NewColorSubdiv[Index].Count -= Count;
314
59.8k
    NewColorSubdiv[*NewColorMapSize].NumEntries =
315
59.8k
        NewColorSubdiv[Index].NumEntries - NumEntries;
316
59.8k
    NewColorSubdiv[Index].NumEntries = NumEntries;
317
239k
    for (j = 0; j < 3; j++) {
318
179k
      NewColorSubdiv[*NewColorMapSize].RGBMin[j] =
319
179k
          NewColorSubdiv[Index].RGBMin[j];
320
179k
      NewColorSubdiv[*NewColorMapSize].RGBWidth[j] =
321
179k
          NewColorSubdiv[Index].RGBWidth[j];
322
179k
    }
323
59.8k
    NewColorSubdiv[*NewColorMapSize].RGBWidth[SortRGBAxis] =
324
59.8k
        NewColorSubdiv[*NewColorMapSize].RGBMin[SortRGBAxis] +
325
59.8k
        NewColorSubdiv[*NewColorMapSize].RGBWidth[SortRGBAxis] -
326
59.8k
        MinColor;
327
59.8k
    NewColorSubdiv[*NewColorMapSize].RGBMin[SortRGBAxis] = MinColor;
328
329
59.8k
    NewColorSubdiv[Index].RGBWidth[SortRGBAxis] =
330
59.8k
        MaxColor - NewColorSubdiv[Index].RGBMin[SortRGBAxis];
331
332
59.8k
    (*NewColorMapSize)++;
333
59.8k
  }
334
335
190
  return GIF_OK;
336
363
}
337
338
/****************************************************************************
339
 Routine called by qsort to compare two entries.
340
 *****************************************************************************/
341
342
178M
static int SortCmpRtn(const void *Entry1, const void *Entry2) {
343
178M
  QuantizedColorType *entry1 = (*((QuantizedColorType **)Entry1));
344
178M
  QuantizedColorType *entry2 = (*((QuantizedColorType **)Entry2));
345
346
  /* sort on all axes of the color space! */
347
178M
  int hash1 = entry1->RGB[SortRGBAxis] * 256 * 256 +
348
178M
              entry1->RGB[(SortRGBAxis + 1) % 3] * 256 +
349
178M
              entry1->RGB[(SortRGBAxis + 2) % 3];
350
178M
  int hash2 = entry2->RGB[SortRGBAxis] * 256 * 256 +
351
178M
              entry2->RGB[(SortRGBAxis + 1) % 3] * 256 +
352
178M
              entry2->RGB[(SortRGBAxis + 2) % 3];
353
354
178M
  return hash1 - hash2;
355
178M
}
356
357
/* end */