Coverage Report

Created: 2025-08-28 06:27

/src/giflib-code/quantize.c
Line
Count
Source (jump to first uncovered line)
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.2M
#define ABS(x) ((x) > 0 ? (x) : (-(x)))
23
24
22.4M
#define COLOR_ARRAY_SIZE 32768
25
100M
#define BITS_PER_PRIM_COLOR 5
26
22.4M
#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
343
                      GifColorType *OutputColorMap) {
67
68
343
  unsigned int Index, NumOfEntries;
69
343
  int i, j, MaxRGBError[3];
70
343
  unsigned int NewColorMapSize;
71
343
  long Red, Green, Blue;
72
343
  NewColorMapType NewColorSubdiv[256];
73
343
  QuantizedColorType *ColorArrayEntries, *QuantizedColor;
74
75
343
  ColorArrayEntries = (QuantizedColorType *)malloc(
76
343
      sizeof(QuantizedColorType) * COLOR_ARRAY_SIZE);
77
343
  if (ColorArrayEntries == NULL) {
78
0
    return GIF_ERROR;
79
0
  }
80
81
11.2M
  for (i = 0; i < COLOR_ARRAY_SIZE; i++) {
82
11.2M
    ColorArrayEntries[i].RGB[0] = i >> (2 * BITS_PER_PRIM_COLOR);
83
11.2M
    ColorArrayEntries[i].RGB[1] =
84
11.2M
        (i >> BITS_PER_PRIM_COLOR) & MAX_PRIM_COLOR;
85
11.2M
    ColorArrayEntries[i].RGB[2] = i & MAX_PRIM_COLOR;
86
11.2M
    ColorArrayEntries[i].Count = 0;
87
11.2M
  }
88
89
  /* Sample the colors and their distribution: */
90
7.75M
  for (i = 0; i < (int)(Width * Height); i++) {
91
7.75M
    Index = ((RedInput[i] >> (8 - BITS_PER_PRIM_COLOR))
92
7.75M
             << (2 * BITS_PER_PRIM_COLOR)) +
93
7.75M
            ((GreenInput[i] >> (8 - BITS_PER_PRIM_COLOR))
94
7.75M
             << BITS_PER_PRIM_COLOR) +
95
7.75M
            (BlueInput[i] >> (8 - BITS_PER_PRIM_COLOR));
96
7.75M
    ColorArrayEntries[Index].Count++;
97
7.75M
  }
98
99
  /* Put all the colors in the first entry of the color map, and call the
100
   * recursive subdivision process.  */
101
88.1k
  for (i = 0; i < 256; i++) {
102
87.8k
    NewColorSubdiv[i].QuantizedColors = NULL;
103
87.8k
    NewColorSubdiv[i].Count = NewColorSubdiv[i].NumEntries = 0;
104
351k
    for (j = 0; j < 3; j++) {
105
263k
      NewColorSubdiv[i].RGBMin[j] = 0;
106
263k
      NewColorSubdiv[i].RGBWidth[j] = 255;
107
263k
    }
108
87.8k
  }
109
110
  /* Find the non empty entries in the color table and chain them: */
111
754k
  for (i = 0; i < COLOR_ARRAY_SIZE; i++) {
112
754k
    if (ColorArrayEntries[i].Count > 0) {
113
343
      break;
114
343
    }
115
754k
  }
116
343
  QuantizedColor = NewColorSubdiv[0].QuantizedColors =
117
343
      &ColorArrayEntries[i];
118
343
  NumOfEntries = 1;
119
10.4M
  while (++i < COLOR_ARRAY_SIZE) {
120
10.4M
    if (ColorArrayEntries[i].Count > 0) {
121
1.46M
      QuantizedColor->Pnext = &ColorArrayEntries[i];
122
1.46M
      QuantizedColor = &ColorArrayEntries[i];
123
1.46M
      NumOfEntries++;
124
1.46M
    }
125
10.4M
  }
126
343
  QuantizedColor->Pnext = NULL;
127
128
343
  NewColorSubdiv[0].NumEntries =
129
343
      NumOfEntries; /* Different sampled colors */
130
343
  NewColorSubdiv[0].Count = ((long)Width) * Height; /* Pixels */
131
343
  NewColorMapSize = 1;
132
343
  if (SubdivColorMap(NewColorSubdiv, *ColorMapSize, &NewColorMapSize) !=
133
343
      GIF_OK) {
134
0
    free((char *)ColorArrayEntries);
135
0
    return GIF_ERROR;
136
0
  }
137
343
  if (NewColorMapSize < *ColorMapSize) {
138
    /* And clear rest of color map: */
139
32.6k
    for (i = NewColorMapSize; i < *ColorMapSize; i++) {
140
32.4k
      OutputColorMap[i].Red = OutputColorMap[i].Green =
141
32.4k
          OutputColorMap[i].Blue = 0;
142
32.4k
    }
143
175
  }
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
55.6k
  for (i = 0; i < NewColorMapSize; i++) {
148
55.3k
    if ((j = NewColorSubdiv[i].NumEntries) > 0) {
149
55.3k
      QuantizedColor = NewColorSubdiv[i].QuantizedColors;
150
55.3k
      Red = Green = Blue = 0;
151
1.52M
      while (QuantizedColor) {
152
1.46M
        QuantizedColor->NewColorIndex = i;
153
1.46M
        Red += QuantizedColor->RGB[0];
154
1.46M
        Green += QuantizedColor->RGB[1];
155
1.46M
        Blue += QuantizedColor->RGB[2];
156
1.46M
        QuantizedColor = QuantizedColor->Pnext;
157
1.46M
      }
158
55.3k
      OutputColorMap[i].Red =
159
55.3k
          (Red << (8 - BITS_PER_PRIM_COLOR)) / j;
160
55.3k
      OutputColorMap[i].Green =
161
55.3k
          (Green << (8 - BITS_PER_PRIM_COLOR)) / j;
162
55.3k
      OutputColorMap[i].Blue =
163
55.3k
          (Blue << (8 - BITS_PER_PRIM_COLOR)) / j;
164
55.3k
    }
165
55.3k
  }
166
167
  /* Finally scan the input buffer again and put the mapped index in the
168
   * output buffer.  */
169
343
  MaxRGBError[0] = MaxRGBError[1] = MaxRGBError[2] = 0;
170
7.75M
  for (i = 0; i < (int)(Width * Height); i++) {
171
7.75M
    Index = ((RedInput[i] >> (8 - BITS_PER_PRIM_COLOR))
172
7.75M
             << (2 * BITS_PER_PRIM_COLOR)) +
173
7.75M
            ((GreenInput[i] >> (8 - BITS_PER_PRIM_COLOR))
174
7.75M
             << BITS_PER_PRIM_COLOR) +
175
7.75M
            (BlueInput[i] >> (8 - BITS_PER_PRIM_COLOR));
176
7.75M
    Index = ColorArrayEntries[Index].NewColorIndex;
177
7.75M
    OutputBuffer[i] = Index;
178
7.75M
    if (MaxRGBError[0] <
179
7.75M
        ABS(OutputColorMap[Index].Red - RedInput[i])) {
180
1.50k
      MaxRGBError[0] =
181
1.50k
          ABS(OutputColorMap[Index].Red - RedInput[i]);
182
1.50k
    }
183
7.75M
    if (MaxRGBError[1] <
184
7.75M
        ABS(OutputColorMap[Index].Green - GreenInput[i])) {
185
1.61k
      MaxRGBError[1] =
186
1.61k
          ABS(OutputColorMap[Index].Green - GreenInput[i]);
187
1.61k
    }
188
7.75M
    if (MaxRGBError[2] <
189
7.75M
        ABS(OutputColorMap[Index].Blue - BlueInput[i])) {
190
1.63k
      MaxRGBError[2] =
191
1.63k
          ABS(OutputColorMap[Index].Blue - BlueInput[i]);
192
1.63k
    }
193
7.75M
  }
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
343
  free((char *)ColorArrayEntries);
202
203
343
  *ColorMapSize = NewColorMapSize;
204
205
343
  return GIF_OK;
206
343
}
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
343
                          unsigned int *NewColorMapSize) {
217
218
343
  unsigned int i, j, Index = 0;
219
343
  QuantizedColorType *QuantizedColor, **SortArray;
220
221
55.3k
  while (ColorMapSize > *NewColorMapSize) {
222
    /* Find candidate for subdivision: */
223
55.1k
    long Sum, Count;
224
55.1k
    int MaxSize = -1;
225
55.1k
    unsigned int NumEntries, MinColor, MaxColor;
226
6.33M
    for (i = 0; i < *NewColorMapSize; i++) {
227
25.1M
      for (j = 0; j < 3; j++) {
228
18.8M
        if ((((int)NewColorSubdiv[i].RGBWidth[j]) >
229
18.8M
             MaxSize) &&
230
18.8M
            (NewColorSubdiv[i].NumEntries > 1)) {
231
200k
          MaxSize = NewColorSubdiv[i].RGBWidth[j];
232
200k
          Index = i;
233
200k
          SortRGBAxis = j;
234
200k
        }
235
18.8M
      }
236
6.27M
    }
237
238
55.1k
    if (MaxSize == -1) {
239
175
      return GIF_OK;
240
175
    }
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
54.9k
    SortArray = (QuantizedColorType **)malloc(
247
54.9k
        sizeof(QuantizedColorType *) *
248
54.9k
        NewColorSubdiv[Index].NumEntries);
249
54.9k
    if (SortArray == NULL) {
250
0
      return GIF_ERROR;
251
0
    }
252
54.9k
    for (j = 0,
253
54.9k
        QuantizedColor = NewColorSubdiv[Index].QuantizedColors;
254
25.9M
         j < NewColorSubdiv[Index].NumEntries &&
255
25.9M
         QuantizedColor != NULL;
256
25.8M
         j++, QuantizedColor = QuantizedColor->Pnext) {
257
25.8M
      SortArray[j] = QuantizedColor;
258
25.8M
    }
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
54.9k
    qsort(SortArray, NewColorSubdiv[Index].NumEntries,
272
54.9k
          sizeof(QuantizedColorType *), SortCmpRtn);
273
274
    /* Relink the sorted list into one: */
275
25.8M
    for (j = 0; j < NewColorSubdiv[Index].NumEntries - 1; j++) {
276
25.8M
      SortArray[j]->Pnext = SortArray[j + 1];
277
25.8M
    }
278
54.9k
    SortArray[NewColorSubdiv[Index].NumEntries - 1]->Pnext = NULL;
279
54.9k
    NewColorSubdiv[Index].QuantizedColors = QuantizedColor =
280
54.9k
        SortArray[0];
281
54.9k
    free((char *)SortArray);
282
283
    /* Now simply add the Counts until we have half of the Count: */
284
54.9k
    Sum = NewColorSubdiv[Index].Count / 2 - QuantizedColor->Count;
285
54.9k
    NumEntries = 1;
286
54.9k
    Count = QuantizedColor->Count;
287
5.42M
    while (QuantizedColor->Pnext != NULL &&
288
5.42M
           (Sum -= QuantizedColor->Pnext->Count) >= 0 &&
289
5.42M
           QuantizedColor->Pnext->Pnext != NULL) {
290
5.36M
      QuantizedColor = QuantizedColor->Pnext;
291
5.36M
      NumEntries++;
292
5.36M
      Count += QuantizedColor->Count;
293
5.36M
    }
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
54.9k
    MaxColor =
300
54.9k
        QuantizedColor->RGB[SortRGBAxis]; /* Max. of first half */
301
    /* coverity[var_deref_op] */
302
54.9k
    MinColor =
303
        // cppcheck-suppress nullPointerRedundantCheck
304
54.9k
        QuantizedColor->Pnext->RGB[SortRGBAxis]; /* of second */
305
54.9k
    MaxColor <<= (8 - BITS_PER_PRIM_COLOR);
306
54.9k
    MinColor <<= (8 - BITS_PER_PRIM_COLOR);
307
308
    /* Partition right here: */
309
54.9k
    NewColorSubdiv[*NewColorMapSize].QuantizedColors =
310
54.9k
        QuantizedColor->Pnext;
311
54.9k
    QuantizedColor->Pnext = NULL;
312
54.9k
    NewColorSubdiv[*NewColorMapSize].Count = Count;
313
54.9k
    NewColorSubdiv[Index].Count -= Count;
314
54.9k
    NewColorSubdiv[*NewColorMapSize].NumEntries =
315
54.9k
        NewColorSubdiv[Index].NumEntries - NumEntries;
316
54.9k
    NewColorSubdiv[Index].NumEntries = NumEntries;
317
219k
    for (j = 0; j < 3; j++) {
318
164k
      NewColorSubdiv[*NewColorMapSize].RGBMin[j] =
319
164k
          NewColorSubdiv[Index].RGBMin[j];
320
164k
      NewColorSubdiv[*NewColorMapSize].RGBWidth[j] =
321
164k
          NewColorSubdiv[Index].RGBWidth[j];
322
164k
    }
323
54.9k
    NewColorSubdiv[*NewColorMapSize].RGBWidth[SortRGBAxis] =
324
54.9k
        NewColorSubdiv[*NewColorMapSize].RGBMin[SortRGBAxis] +
325
54.9k
        NewColorSubdiv[*NewColorMapSize].RGBWidth[SortRGBAxis] -
326
54.9k
        MinColor;
327
54.9k
    NewColorSubdiv[*NewColorMapSize].RGBMin[SortRGBAxis] = MinColor;
328
329
54.9k
    NewColorSubdiv[Index].RGBWidth[SortRGBAxis] =
330
54.9k
        MaxColor - NewColorSubdiv[Index].RGBMin[SortRGBAxis];
331
332
54.9k
    (*NewColorMapSize)++;
333
54.9k
  }
334
335
168
  return GIF_OK;
336
343
}
337
338
/****************************************************************************
339
 Routine called by qsort to compare two entries.
340
 *****************************************************************************/
341
342
175M
static int SortCmpRtn(const void *Entry1, const void *Entry2) {
343
175M
  QuantizedColorType *entry1 = (*((QuantizedColorType **)Entry1));
344
175M
  QuantizedColorType *entry2 = (*((QuantizedColorType **)Entry2));
345
346
  /* sort on all axes of the color space! */
347
175M
  int hash1 = entry1->RGB[SortRGBAxis] * 256 * 256 +
348
175M
              entry1->RGB[(SortRGBAxis + 1) % 3] * 256 +
349
175M
              entry1->RGB[(SortRGBAxis + 2) % 3];
350
175M
  int hash2 = entry2->RGB[SortRGBAxis] * 256 * 256 +
351
175M
              entry2->RGB[(SortRGBAxis + 1) % 3] * 256 +
352
175M
              entry2->RGB[(SortRGBAxis + 2) % 3];
353
354
175M
  return hash1 - hash2;
355
175M
}
356
357
/* end */