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