1 /*=============================================================================
2                                     pamtoxvmini
3 ===============================================================================
4    Convert Netpbm image to XV mini thumbnail.
5 
6    Written by Bryan Henderson in April 2006 and contributed to the public
7    domain.
8 =============================================================================*/
9 
10 #include <assert.h>
11 #include <limits.h>
12 #include <string.h>
13 
14 #include "pm_c_util.h"
15 #include "nstring.h"
16 #include "pam.h"
17 #include "pammap.h"
18 
19 typedef struct xvPalette {
20     unsigned int red[256];
21     unsigned int grn[256];
22     unsigned int blu[256];
23 } xvPalette;
24 
25 
26 struct cmdlineInfo {
27     const char * inputFileName;
28 };
29 
30 
31 
32 static void
parseCommandLine(int const argc,char * argv[],struct cmdlineInfo * const cmdlineP)33 parseCommandLine(int const argc,
34                  char *    argv[],
35                  struct cmdlineInfo * const cmdlineP) {
36 
37     if (argc-1 < 1)
38         cmdlineP->inputFileName = "-";
39     else {
40         cmdlineP->inputFileName = argv[1];
41 
42         if (argc-1 > 1)
43             pm_error("Too many arguments: %u.  Only argument is optional "
44                      "input file name.", argc-1);
45     }
46 }
47 
48 
49 
50 static void
makeXvPalette(xvPalette * const xvPaletteP)51 makeXvPalette(xvPalette * const xvPaletteP) {
52 
53     unsigned int paletteIndex;
54     unsigned int r;
55 
56     paletteIndex = 0;
57 
58     for (r = 0; r < 8; ++r) {
59         unsigned int g;
60         for (g = 0; g < 8; ++g) {
61             unsigned int b;
62             for (b = 0; b < 4; ++b) {
63                 xvPaletteP->red[paletteIndex] = (r*255)/7;
64                 xvPaletteP->grn[paletteIndex] = (g*255)/7;
65                 xvPaletteP->blu[paletteIndex] = (b*255)/3;
66                 ++paletteIndex;
67             }
68         }
69     }
70 }
71 
72 
73 
74 static void
writeXvHeader(FILE * const ofP,unsigned int const cols,unsigned int const rows)75 writeXvHeader(FILE *       const ofP,
76               unsigned int const cols,
77               unsigned int const rows) {
78 
79     fprintf(ofP, "P7 332\n");
80 
81     fprintf(ofP, "# Created by Pamtoxvmini\n");
82     fprintf(ofP, "#END_OF_COMMENTS\n");
83 
84     /* I don't know what the maxval number (3rd field) means here, since
85        the maxvals are fixed at red=7, grn=7, blu=3.  We used to have
86        it put the maxval of the input image there.  That generated an
87        output that Xv choked on when the input maxval was 65535.
88     */
89 
90     fprintf(ofP, "%u %u 255\n", cols, rows);
91 }
92 
93 
94 
95 static void
findClosestColor(struct pam * const pamP,tuple const tuple,const xvPalette * const xvPaletteP,unsigned int * const paletteIndexP)96 findClosestColor(struct pam *      const pamP,
97                  tuple             const tuple,
98                  const xvPalette * const xvPaletteP,
99                  unsigned int *    const paletteIndexP) {
100 /*----------------------------------------------------------------------------
101    Find the color in the palette *xvPaletteP that is closest to the color
102    'tuple' and return its index in the palette.
103 -----------------------------------------------------------------------------*/
104     unsigned int paletteIndex;
105     unsigned int bestPaletteIndex;
106     unsigned int bestDistanceSoFar;
107 
108     /* An entry condition is that the tuple have the same form as the
109        colors in the XV palette:
110     */
111     assert(pamP->depth >= 3);
112     assert(pamP->maxval == 255);
113 
114     bestPaletteIndex = 0;
115     bestDistanceSoFar = UINT_MAX;
116 
117     for (paletteIndex = 0; paletteIndex < 256; ++paletteIndex) {
118         unsigned int const tupleRed = tuple[PAM_RED_PLANE];
119         unsigned int const tupleGrn = tuple[PAM_GRN_PLANE];
120         unsigned int const tupleBlu = tuple[PAM_BLU_PLANE];
121 
122         unsigned int const paletteRed = xvPaletteP->red[paletteIndex];
123         unsigned int const paletteGrn = xvPaletteP->grn[paletteIndex];
124         unsigned int const paletteBlu = xvPaletteP->blu[paletteIndex];
125 
126         unsigned int const distance =
127             SQR((int)tupleRed - (int)paletteRed) +
128             SQR((int)tupleGrn - (int)paletteGrn) +
129             SQR((int)tupleBlu - (int)paletteBlu);
130 
131         if (distance < bestDistanceSoFar) {
132             bestDistanceSoFar = distance;
133             bestPaletteIndex = paletteIndex;
134         }
135     }
136     *paletteIndexP = bestPaletteIndex;
137 }
138 
139 
140 
141 static void
getPaletteIndexThroughCache(struct pam * const pamP,tuple const tuple,const xvPalette * const xvPaletteP,tuplehash const paletteHash,unsigned int * const paletteIndexP)142 getPaletteIndexThroughCache(struct pam *      const pamP,
143                             tuple             const tuple,
144                             const xvPalette * const xvPaletteP,
145                             tuplehash         const paletteHash,
146                             unsigned int *    const paletteIndexP) {
147 /*----------------------------------------------------------------------------
148    Return as *paletteIndexP the index into the palette *xvPaletteP of
149    the color that most closely resembles the color 'tuple'.
150 
151    Use the hash table *paletteIndexP as a cache to speed up the search.
152    If the tuple-index association is in *paletteIndexP, use it.  If not,
153    find it the hard way and add it to *palettedIndexP for the next guy.
154 -----------------------------------------------------------------------------*/
155     int found;
156     int paletteIndex;
157 
158     pnm_lookuptuple(pamP, paletteHash, tuple, &found, &paletteIndex);
159     if (found)
160         *paletteIndexP = paletteIndex;
161     else {
162         int fits;
163         findClosestColor(pamP, tuple, xvPaletteP, paletteIndexP);
164 
165         pnm_addtotuplehash(pamP, paletteHash, tuple, *paletteIndexP, &fits);
166 
167         if (!fits)
168             pm_error("Can't get memory for palette hash.");
169     }
170 }
171 
172 
173 
174 static void
writeXvRaster(struct pam * const pamP,xvPalette * const xvPaletteP,FILE * const ofP)175 writeXvRaster(struct pam * const pamP,
176               xvPalette *  const xvPaletteP,
177               FILE *       const ofP) {
178 /*----------------------------------------------------------------------------
179    Write out the XV image, from the Netpbm input file ifP, which is
180    positioned to the raster.
181 
182    The XV raster contains palette indices into the palette *xvPaletteP.
183 
184    If there is any color in the image which is not in the palette, we
185    fail the program.  We really should use the closest color in the palette
186    instead.
187 -----------------------------------------------------------------------------*/
188     tuplehash paletteHash;
189     tuple * tuplerow;
190     unsigned int row;
191     unsigned char * xvrow;
192     struct pam scaledPam;
193 
194     paletteHash = pnm_createtuplehash();
195 
196     tuplerow = pnm_allocpamrow(pamP);
197     xvrow = (unsigned char*)pm_allocrow(pamP->width, 1);
198 
199     scaledPam = *pamP;
200     scaledPam.maxval = 255;
201 
202     for (row = 0; row < pamP->height; ++row) {
203         unsigned int col;
204 
205         pnm_readpamrow(pamP, tuplerow);
206         pnm_scaletuplerow(pamP, tuplerow, tuplerow, scaledPam.maxval);
207         pnm_makerowrgb(&scaledPam, tuplerow);
208 
209         for (col = 0; col < scaledPam.width; ++col) {
210             unsigned int paletteIndex;
211 
212             getPaletteIndexThroughCache(&scaledPam, tuplerow[col], xvPaletteP,
213                                         paletteHash, &paletteIndex);
214 
215             assert(paletteIndex < 256);
216 
217             xvrow[col] = paletteIndex;
218         }
219         fwrite(xvrow, 1, scaledPam.width, ofP);
220     }
221 
222     pm_freerow((char*)xvrow);
223     pnm_freepamrow(tuplerow);
224 
225     pnm_destroytuplehash(paletteHash);
226 }
227 
228 
229 
230 int
main(int argc,char * argv[])231 main(int    argc,
232      char * argv[]) {
233 
234     struct cmdlineInfo cmdline;
235     FILE * ifP;
236     struct pam pam;
237     xvPalette xvPalette;
238 
239     pnm_init(&argc, argv);
240 
241     parseCommandLine(argc, argv, &cmdline);
242 
243     ifP = pm_openr(cmdline.inputFileName);
244 
245     makeXvPalette(&xvPalette);
246 
247     pnm_readpaminit(ifP, &pam, PAM_STRUCT_SIZE(allocation_depth));
248 
249     pnm_setminallocationdepth(&pam, 3);
250 
251     writeXvHeader(stdout, pam.width, pam.height);
252 
253     writeXvRaster(&pam, &xvPalette, stdout);
254 
255     pm_close(ifP);
256 
257     return 0;
258 }
259