1 /*****************************************************************************
2
3 gifhisto - make a color histogram from image color frequencies
4
5 SPDX-License-Identifier: MIT
6
7 *****************************************************************************/
8
9 #include <stdlib.h>
10 #include <stdio.h>
11 #include <ctype.h>
12 #include <string.h>
13 #include <stdbool.h>
14
15 #include "gif_lib.h"
16 #include "getarg.h"
17
18 #define PROGRAM_NAME "gifhisto"
19
20 #define DEFAULT_HISTO_WIDTH 100 /* Histogram image diemnsions. */
21 #define DEFAULT_HISTO_HEIGHT 256
22 #define HISTO_BITS_PER_PIXEL 2 /* Size of bitmap for histogram GIF. */
23
24 static char
25 *VersionStr =
26 PROGRAM_NAME
27 VERSION_COOKIE
28 " Gershon Elber, "
29 __DATE__ ", " __TIME__ "\n"
30 "(C) Copyright 1989 Gershon Elber.\n";
31 static char
32 *CtrlStr =
33 PROGRAM_NAME
34 " v%- t%- s%-Width|Height!d!d n%-ImageNumber!d b%- h%- GifFile!*s";
35
36 static int
37 ImageWidth = DEFAULT_HISTO_WIDTH,
38 ImageHeight = DEFAULT_HISTO_HEIGHT,
39 ImageN = 1;
40 static GifColorType
41 HistoColorMap[] = { /* Constant bit map for histograms: */
42 { 0, 0, 0 },
43 { 255, 0, 0 },
44 { 0, 255, 0 },
45 { 0, 0, 255 }
46 };
47
48 static void QuitGifError(GifFileType *GifFileIn, GifFileType *GifFileOut);
49
50 /******************************************************************************
51 Interpret the command line and scan the given GIF file.
52 ******************************************************************************/
main(int argc,char ** argv)53 int main(int argc, char **argv)
54 {
55 int i, j, ErrorCode, NumFiles, ExtCode, CodeSize, NumColors = 2, ImageNum = 0;
56 bool Error, TextFlag = false, SizeFlag = false,
57 ImageNFlag = false, BackGroundFlag = false, HelpFlag = false;
58 long Histogram[256];
59 GifRecordType RecordType;
60 GifByteType *Extension, *CodeBlock;
61 char **FileName = NULL;
62 GifRowType Line;
63 GifFileType *GifFileIn = NULL, *GifFileOut = NULL;
64
65 /* Same image dimension vars for both Image & ImageN as only one allowed */
66 if ((Error = GAGetArgs(argc, argv, CtrlStr, &GifNoisyPrint,
67 &TextFlag, &SizeFlag, &ImageWidth, &ImageHeight,
68 &ImageNFlag, &ImageN, &BackGroundFlag,
69 &HelpFlag, &NumFiles, &FileName)) != false ||
70 (NumFiles > 1 && !HelpFlag)) {
71 if (Error)
72 GAPrintErrMsg(Error);
73 else if (NumFiles > 1)
74 GIF_MESSAGE("Error in command line parsing - one GIF file please.");
75 GAPrintHowTo(CtrlStr);
76 exit(EXIT_FAILURE);
77 }
78
79 if (HelpFlag) {
80 (void)fprintf(stderr, VersionStr, GIFLIB_MAJOR, GIFLIB_MINOR);
81 GAPrintHowTo(CtrlStr);
82 exit(EXIT_SUCCESS);
83 }
84
85 if (NumFiles == 1) {
86 if ((GifFileIn = DGifOpenFileName(*FileName, &ErrorCode)) == NULL) {
87 PrintGifError(ErrorCode);
88 exit(EXIT_FAILURE);
89 }
90 }
91 else {
92 /* Use stdin instead: */
93 if ((GifFileIn = DGifOpenFileHandle(0, &ErrorCode)) == NULL) {
94 PrintGifError(ErrorCode);
95 exit(EXIT_FAILURE);
96 }
97 }
98
99 for (i = 0; i < 256; i++) Histogram[i] = 0; /* Reset counters. */
100
101 /* Scan the content of the GIF file and load the image(s) in: */
102 do {
103 if (DGifGetRecordType(GifFileIn, &RecordType) == GIF_ERROR)
104 QuitGifError(GifFileIn, GifFileOut);
105
106 switch (RecordType) {
107 case IMAGE_DESC_RECORD_TYPE:
108 if (DGifGetImageDesc(GifFileIn) == GIF_ERROR)
109 QuitGifError(GifFileIn, GifFileOut);
110
111 if (GifFileIn->Image.ColorMap)
112 NumColors = GifFileIn->Image.ColorMap->ColorCount;
113 else if (GifFileIn->SColorMap)
114 NumColors = GifFileIn->SColorMap->ColorCount;
115 else
116 GIF_EXIT("Neither Screen nor Image color map exists.");
117
118 if ((ImageHeight / NumColors) * NumColors != ImageHeight)
119 GIF_EXIT("Image height specified not dividable by #colors.");
120
121 if (++ImageNum == ImageN) {
122 /* This is the image we should make histogram for: */
123 Line = (GifRowType) malloc(GifFileIn->Image.Width *
124 sizeof(GifPixelType));
125 GifQprintf("\n%s: Image %d at (%d, %d) [%dx%d]: ",
126 PROGRAM_NAME, ImageNum,
127 GifFileIn->Image.Left, GifFileIn->Image.Top,
128 GifFileIn->Image.Width, GifFileIn->Image.Height);
129
130 for (i = 0; i < GifFileIn->Image.Height; i++) {
131 if (DGifGetLine(GifFileIn, Line, GifFileIn->Image.Width)
132 == GIF_ERROR)
133 QuitGifError(GifFileIn, GifFileOut);
134 for (j = 0; j < GifFileIn->Image.Width; j++)
135 Histogram[Line[j]]++;
136 GifQprintf("\b\b\b\b%-4d", i);
137 }
138
139 free((char *) Line);
140 }
141 else {
142 /* Skip the image: */
143 /* Now read image itself in decoded form as we dont */
144 /* really care what is there, and this is much faster. */
145 if (DGifGetCode(GifFileIn, &CodeSize, &CodeBlock) == GIF_ERROR)
146 QuitGifError(GifFileIn, GifFileOut);
147 while (CodeBlock != NULL)
148 if (DGifGetCodeNext(GifFileIn, &CodeBlock) == GIF_ERROR)
149 QuitGifError(GifFileIn, GifFileOut);
150 }
151 break;
152 case EXTENSION_RECORD_TYPE:
153 /* Skip any extension blocks in file: */
154 if (DGifGetExtension(GifFileIn, &ExtCode, &Extension) == GIF_ERROR)
155 QuitGifError(GifFileIn, GifFileOut);
156
157 while (Extension != NULL) {
158 if (DGifGetExtensionNext(GifFileIn, &Extension) == GIF_ERROR)
159 QuitGifError(GifFileIn, GifFileOut);
160 }
161 break;
162 case TERMINATE_RECORD_TYPE:
163 break;
164 default: /* Should be trapped by DGifGetRecordType. */
165 break;
166 }
167 }
168 while (RecordType != TERMINATE_RECORD_TYPE);
169
170 /* We requested suppression of the background count: */
171 if (BackGroundFlag) Histogram[GifFileIn->SBackGroundColor] = 0;
172
173 if (DGifCloseFile(GifFileIn, &ErrorCode) == GIF_ERROR)
174 {
175 PrintGifError(ErrorCode);
176 exit(EXIT_FAILURE);
177 }
178
179
180 /* We may required to dump out the histogram as text file: */
181 if (TextFlag) {
182 for (i = 0; i < NumColors; i++)
183 printf("%12ld %3d\n", Histogram[i], i);
184 }
185 else {
186 int Color, Count;
187 long Scaler;
188 /* Open stdout for the histogram output file: */
189 if ((GifFileOut = EGifOpenFileHandle(1, &ErrorCode)) == NULL) {
190 PrintGifError(ErrorCode);
191 exit(EXIT_FAILURE);
192 }
193
194 /* Dump out screen descriptor to fit histogram dimensions: */
195 if (EGifPutScreenDesc(GifFileOut,
196 ImageWidth, ImageHeight, HISTO_BITS_PER_PIXEL, 0,
197 GifMakeMapObject(4, HistoColorMap)) == GIF_ERROR)
198 QuitGifError(GifFileIn, GifFileOut);
199
200 /* Dump out image descriptor to fit histogram dimensions: */
201 if (EGifPutImageDesc(GifFileOut,
202 0, 0, ImageWidth, ImageHeight, false, NULL) == GIF_ERROR)
203 QuitGifError(GifFileIn, GifFileOut);
204
205 /* Prepare scan line for histogram file, and find scaler to scale */
206 /* histogram to be between 0 and ImageWidth: */
207 Line = (GifRowType) malloc(ImageWidth * sizeof(GifPixelType));
208 for (Scaler = 0, i = 0; i < NumColors; i++) if (Histogram[i] > Scaler)
209 Scaler = Histogram[i];
210 Scaler /= ImageWidth;
211 if (Scaler == 0) Scaler = 1; /* In case maximum is less than width. */
212
213 /* Dump out the image itself: */
214 for (Count = ImageHeight, i = 0, Color = 1; i < NumColors; i++) {
215 int Size;
216 if ((Size = Histogram[i] / Scaler) > ImageWidth) Size = ImageWidth;
217 for (j = 0; j < Size; j++)
218 Line[j] = Color;
219 for (j = Size; j < ImageWidth; j++)
220 Line[j] = GifFileOut->SBackGroundColor;
221
222 /* Move to next color: */
223 if (++Color >= (1 << HISTO_BITS_PER_PIXEL)) Color = 1;
224
225 /* Dump this histogram entry as many times as required: */
226 for (j = 0; j < ImageHeight / NumColors; j++) {
227 if (EGifPutLine(GifFileOut, Line, ImageWidth) == GIF_ERROR)
228 QuitGifError(GifFileIn, GifFileOut);
229 GifQprintf("\b\b\b\b%-4d", Count--);
230 }
231 }
232
233 if (EGifCloseFile(GifFileOut, &ErrorCode) == GIF_ERROR)
234 {
235 PrintGifError(ErrorCode);
236 exit(EXIT_FAILURE);
237 }
238 }
239
240 return 0;
241 }
242
243 /******************************************************************************
244 Close both input and output file (if open), and exit.
245 ******************************************************************************/
QuitGifError(GifFileType * GifFileIn,GifFileType * GifFileOut)246 static void QuitGifError(GifFileType *GifFileIn, GifFileType *GifFileOut)
247 {
248 if (GifFileIn != NULL) {
249 PrintGifError(GifFileIn->Error);
250 EGifCloseFile(GifFileIn, NULL);
251 }
252 if (GifFileOut != NULL) {
253 PrintGifError(GifFileOut->Error);
254 EGifCloseFile(GifFileOut, NULL);
255 }
256 exit(EXIT_FAILURE);
257 }
258
259 /* end */
260