]>
Commit | Line | Data |
---|---|---|
8414a40c VZ |
1 | /* $Id$ */ |
2 | ||
3 | /* | |
4 | * Copyright (c) 1991-1997 Sam Leffler | |
5 | * Copyright (c) 1991-1997 Silicon Graphics, Inc. | |
6 | * | |
7 | * Permission to use, copy, modify, distribute, and sell this software and | |
8 | * its documentation for any purpose is hereby granted without fee, provided | |
9 | * that (i) the above copyright notices and this permission notice appear in | |
10 | * all copies of the software and related documentation, and (ii) the names of | |
11 | * Sam Leffler and Silicon Graphics may not be used in any advertising or | |
12 | * publicity relating to the software without the specific, prior written | |
13 | * permission of Sam Leffler and Silicon Graphics. | |
14 | * | |
15 | * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, | |
16 | * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY | |
17 | * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. | |
18 | * | |
19 | * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR | |
20 | * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, | |
21 | * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, | |
22 | * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF | |
23 | * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |
24 | * OF THIS SOFTWARE. | |
25 | */ | |
26 | ||
27 | #include "tif_config.h" | |
28 | ||
29 | #include <stdio.h> | |
30 | #include <string.h> | |
31 | #include <stdlib.h> | |
32 | ||
33 | #ifdef HAVE_UNISTD_H | |
34 | # include <unistd.h> | |
35 | #endif | |
36 | ||
80ed523f VZ |
37 | #ifdef NEED_LIBPORT |
38 | # include "libport.h" | |
39 | #endif | |
40 | ||
41 | #include "tiffiop.h" | |
8414a40c VZ |
42 | #include "tiffio.h" |
43 | ||
44 | #define streq(a,b) (strcmp(a,b) == 0) | |
45 | #define CopyField(tag, v) \ | |
46 | if (TIFFGetField(in, tag, &v)) TIFFSetField(out, tag, v) | |
47 | ||
48 | #ifndef howmany | |
49 | #define howmany(x, y) (((x)+((y)-1))/(y)) | |
50 | #endif | |
51 | #define roundup(x, y) (howmany(x,y)*((uint32)(y))) | |
52 | ||
53 | #define LumaRed ycbcrCoeffs[0] | |
54 | #define LumaGreen ycbcrCoeffs[1] | |
55 | #define LumaBlue ycbcrCoeffs[2] | |
56 | ||
57 | uint16 compression = COMPRESSION_PACKBITS; | |
58 | uint32 rowsperstrip = (uint32) -1; | |
59 | ||
60 | uint16 horizSubSampling = 2; /* YCbCr horizontal subsampling */ | |
61 | uint16 vertSubSampling = 2; /* YCbCr vertical subsampling */ | |
62 | float ycbcrCoeffs[3] = { .299F, .587F, .114F }; | |
63 | /* default coding range is CCIR Rec 601-1 with no headroom/footroom */ | |
64 | float refBlackWhite[6] = { 0.F, 255.F, 128.F, 255.F, 128.F, 255.F }; | |
65 | ||
66 | static int tiffcvt(TIFF* in, TIFF* out); | |
67 | static void usage(int code); | |
68 | static void setupLumaTables(void); | |
69 | ||
70 | int | |
71 | main(int argc, char* argv[]) | |
72 | { | |
73 | TIFF *in, *out; | |
74 | int c; | |
75 | extern int optind; | |
76 | extern char *optarg; | |
77 | ||
78 | while ((c = getopt(argc, argv, "c:h:r:v:z")) != -1) | |
79 | switch (c) { | |
80 | case 'c': | |
81 | if (streq(optarg, "none")) | |
82 | compression = COMPRESSION_NONE; | |
83 | else if (streq(optarg, "packbits")) | |
84 | compression = COMPRESSION_PACKBITS; | |
85 | else if (streq(optarg, "lzw")) | |
86 | compression = COMPRESSION_LZW; | |
87 | else if (streq(optarg, "jpeg")) | |
88 | compression = COMPRESSION_JPEG; | |
89 | else if (streq(optarg, "zip")) | |
90 | compression = COMPRESSION_ADOBE_DEFLATE; | |
91 | else | |
92 | usage(-1); | |
93 | break; | |
94 | case 'h': | |
95 | horizSubSampling = atoi(optarg); | |
96 | break; | |
97 | case 'v': | |
98 | vertSubSampling = atoi(optarg); | |
99 | break; | |
100 | case 'r': | |
101 | rowsperstrip = atoi(optarg); | |
102 | break; | |
103 | case 'z': /* CCIR Rec 601-1 w/ headroom/footroom */ | |
104 | refBlackWhite[0] = 16.; | |
105 | refBlackWhite[1] = 235.; | |
106 | refBlackWhite[2] = 128.; | |
107 | refBlackWhite[3] = 240.; | |
108 | refBlackWhite[4] = 128.; | |
109 | refBlackWhite[5] = 240.; | |
110 | break; | |
111 | case '?': | |
112 | usage(0); | |
113 | /*NOTREACHED*/ | |
114 | } | |
115 | if (argc - optind < 2) | |
116 | usage(-1); | |
117 | out = TIFFOpen(argv[argc-1], "w"); | |
118 | if (out == NULL) | |
119 | return (-2); | |
120 | setupLumaTables(); | |
121 | for (; optind < argc-1; optind++) { | |
122 | in = TIFFOpen(argv[optind], "r"); | |
123 | if (in != NULL) { | |
124 | do { | |
125 | if (!tiffcvt(in, out) || | |
126 | !TIFFWriteDirectory(out)) { | |
127 | (void) TIFFClose(out); | |
128 | return (1); | |
129 | } | |
130 | } while (TIFFReadDirectory(in)); | |
131 | (void) TIFFClose(in); | |
132 | } | |
133 | } | |
134 | (void) TIFFClose(out); | |
135 | return (0); | |
136 | } | |
137 | ||
138 | float *lumaRed; | |
139 | float *lumaGreen; | |
140 | float *lumaBlue; | |
141 | float D1, D2; | |
142 | int Yzero; | |
143 | ||
144 | static float* | |
145 | setupLuma(float c) | |
146 | { | |
147 | float *v = (float *)_TIFFmalloc(256 * sizeof (float)); | |
148 | int i; | |
149 | for (i = 0; i < 256; i++) | |
150 | v[i] = c * i; | |
151 | return (v); | |
152 | } | |
153 | ||
154 | static unsigned | |
155 | V2Code(float f, float RB, float RW, int CR) | |
156 | { | |
157 | unsigned int c = (unsigned int)((((f)*(RW-RB)/CR)+RB)+.5); | |
158 | return (c > 255 ? 255 : c); | |
159 | } | |
160 | ||
161 | static void | |
162 | setupLumaTables(void) | |
163 | { | |
164 | lumaRed = setupLuma(LumaRed); | |
165 | lumaGreen = setupLuma(LumaGreen); | |
166 | lumaBlue = setupLuma(LumaBlue); | |
167 | D1 = 1.F/(2.F - 2.F*LumaBlue); | |
168 | D2 = 1.F/(2.F - 2.F*LumaRed); | |
169 | Yzero = V2Code(0, refBlackWhite[0], refBlackWhite[1], 255); | |
170 | } | |
171 | ||
172 | static void | |
173 | cvtClump(unsigned char* op, uint32* raster, uint32 ch, uint32 cw, uint32 w) | |
174 | { | |
175 | float Y, Cb = 0, Cr = 0; | |
176 | uint32 j, k; | |
177 | /* | |
178 | * Convert ch-by-cw block of RGB | |
179 | * to YCbCr and sample accordingly. | |
180 | */ | |
181 | for (k = 0; k < ch; k++) { | |
182 | for (j = 0; j < cw; j++) { | |
183 | uint32 RGB = (raster - k*w)[j]; | |
184 | Y = lumaRed[TIFFGetR(RGB)] + | |
185 | lumaGreen[TIFFGetG(RGB)] + | |
186 | lumaBlue[TIFFGetB(RGB)]; | |
187 | /* accumulate chrominance */ | |
188 | Cb += (TIFFGetB(RGB) - Y) * D1; | |
189 | Cr += (TIFFGetR(RGB) - Y) * D2; | |
190 | /* emit luminence */ | |
191 | *op++ = V2Code(Y, | |
192 | refBlackWhite[0], refBlackWhite[1], 255); | |
193 | } | |
194 | for (; j < horizSubSampling; j++) | |
195 | *op++ = Yzero; | |
196 | } | |
197 | for (; k < vertSubSampling; k++) { | |
198 | for (j = 0; j < horizSubSampling; j++) | |
199 | *op++ = Yzero; | |
200 | } | |
201 | /* emit sampled chrominance values */ | |
202 | *op++ = V2Code(Cb / (ch*cw), refBlackWhite[2], refBlackWhite[3], 127); | |
203 | *op++ = V2Code(Cr / (ch*cw), refBlackWhite[4], refBlackWhite[5], 127); | |
204 | } | |
205 | #undef LumaRed | |
206 | #undef LumaGreen | |
207 | #undef LumaBlue | |
208 | #undef V2Code | |
209 | ||
210 | /* | |
211 | * Convert a strip of RGB data to YCbCr and | |
212 | * sample to generate the output data. | |
213 | */ | |
214 | static void | |
215 | cvtStrip(unsigned char* op, uint32* raster, uint32 nrows, uint32 width) | |
216 | { | |
217 | uint32 x; | |
218 | int clumpSize = vertSubSampling * horizSubSampling + 2; | |
219 | uint32 *tp; | |
220 | ||
221 | for (; nrows >= vertSubSampling; nrows -= vertSubSampling) { | |
222 | tp = raster; | |
223 | for (x = width; x >= horizSubSampling; x -= horizSubSampling) { | |
224 | cvtClump(op, tp, | |
225 | vertSubSampling, horizSubSampling, width); | |
226 | op += clumpSize; | |
227 | tp += horizSubSampling; | |
228 | } | |
229 | if (x > 0) { | |
230 | cvtClump(op, tp, vertSubSampling, x, width); | |
231 | op += clumpSize; | |
232 | } | |
233 | raster -= vertSubSampling*width; | |
234 | } | |
235 | if (nrows > 0) { | |
236 | tp = raster; | |
237 | for (x = width; x >= horizSubSampling; x -= horizSubSampling) { | |
238 | cvtClump(op, tp, nrows, horizSubSampling, width); | |
239 | op += clumpSize; | |
240 | tp += horizSubSampling; | |
241 | } | |
242 | if (x > 0) | |
243 | cvtClump(op, tp, nrows, x, width); | |
244 | } | |
245 | } | |
246 | ||
247 | static int | |
248 | cvtRaster(TIFF* tif, uint32* raster, uint32 width, uint32 height) | |
249 | { | |
250 | uint32 y; | |
251 | tstrip_t strip = 0; | |
252 | tsize_t cc, acc; | |
253 | unsigned char* buf; | |
254 | uint32 rwidth = roundup(width, horizSubSampling); | |
255 | uint32 rheight = roundup(height, vertSubSampling); | |
256 | uint32 nrows = (rowsperstrip > rheight ? rheight : rowsperstrip); | |
257 | uint32 rnrows = roundup(nrows,vertSubSampling); | |
258 | ||
259 | cc = rnrows*rwidth + | |
260 | 2*((rnrows*rwidth) / (horizSubSampling*vertSubSampling)); | |
261 | buf = (unsigned char*)_TIFFmalloc(cc); | |
80ed523f | 262 | // FIXME unchecked malloc |
8414a40c VZ |
263 | for (y = height; (int32) y > 0; y -= nrows) { |
264 | uint32 nr = (y > nrows ? nrows : y); | |
265 | cvtStrip(buf, raster + (y-1)*width, nr, width); | |
266 | nr = roundup(nr, vertSubSampling); | |
267 | acc = nr*rwidth + | |
268 | 2*((nr*rwidth)/(horizSubSampling*vertSubSampling)); | |
269 | if (!TIFFWriteEncodedStrip(tif, strip++, buf, acc)) { | |
270 | _TIFFfree(buf); | |
271 | return (0); | |
272 | } | |
273 | } | |
274 | _TIFFfree(buf); | |
275 | return (1); | |
276 | } | |
277 | ||
278 | static int | |
279 | tiffcvt(TIFF* in, TIFF* out) | |
280 | { | |
281 | uint32 width, height; /* image width & height */ | |
282 | uint32* raster; /* retrieve RGBA image */ | |
283 | uint16 shortv; | |
284 | float floatv; | |
285 | char *stringv; | |
286 | uint32 longv; | |
80ed523f VZ |
287 | int result; |
288 | size_t pixel_count; | |
8414a40c VZ |
289 | |
290 | TIFFGetField(in, TIFFTAG_IMAGEWIDTH, &width); | |
291 | TIFFGetField(in, TIFFTAG_IMAGELENGTH, &height); | |
80ed523f VZ |
292 | pixel_count = width * height; |
293 | ||
294 | /* XXX: Check the integer overflow. */ | |
295 | if (!width || !height || pixel_count / width != height) { | |
296 | TIFFError(TIFFFileName(in), | |
297 | "Malformed input file; " | |
298 | "can't allocate buffer for raster of %lux%lu size", | |
299 | (unsigned long)width, (unsigned long)height); | |
300 | return 0; | |
301 | } | |
302 | ||
303 | raster = (uint32*)_TIFFCheckMalloc(in, pixel_count, sizeof(uint32), | |
304 | "raster buffer"); | |
305 | if (raster == 0) { | |
306 | TIFFError(TIFFFileName(in), | |
307 | "Failed to allocate buffer (%lu elements of %lu each)", | |
308 | (unsigned long)pixel_count, | |
309 | (unsigned long)sizeof(uint32)); | |
310 | return (0); | |
311 | } | |
312 | ||
8414a40c VZ |
313 | if (!TIFFReadRGBAImage(in, width, height, raster, 0)) { |
314 | _TIFFfree(raster); | |
315 | return (0); | |
316 | } | |
317 | ||
318 | CopyField(TIFFTAG_SUBFILETYPE, longv); | |
319 | TIFFSetField(out, TIFFTAG_IMAGEWIDTH, width); | |
320 | TIFFSetField(out, TIFFTAG_IMAGELENGTH, height); | |
321 | TIFFSetField(out, TIFFTAG_BITSPERSAMPLE, 8); | |
322 | TIFFSetField(out, TIFFTAG_COMPRESSION, compression); | |
323 | TIFFSetField(out, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_YCBCR); | |
324 | if (compression == COMPRESSION_JPEG) | |
325 | TIFFSetField(out, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RAW); | |
326 | CopyField(TIFFTAG_FILLORDER, shortv); | |
327 | TIFFSetField(out, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); | |
328 | TIFFSetField(out, TIFFTAG_SAMPLESPERPIXEL, 3); | |
329 | CopyField(TIFFTAG_XRESOLUTION, floatv); | |
330 | CopyField(TIFFTAG_YRESOLUTION, floatv); | |
331 | CopyField(TIFFTAG_RESOLUTIONUNIT, shortv); | |
332 | TIFFSetField(out, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); | |
333 | { char buf[2048]; | |
334 | char *cp = strrchr(TIFFFileName(in), '/'); | |
335 | sprintf(buf, "YCbCr conversion of %s", cp ? cp+1 : TIFFFileName(in)); | |
336 | TIFFSetField(out, TIFFTAG_IMAGEDESCRIPTION, buf); | |
337 | } | |
338 | TIFFSetField(out, TIFFTAG_SOFTWARE, TIFFGetVersion()); | |
339 | CopyField(TIFFTAG_DOCUMENTNAME, stringv); | |
340 | ||
341 | TIFFSetField(out, TIFFTAG_REFERENCEBLACKWHITE, refBlackWhite); | |
342 | TIFFSetField(out, TIFFTAG_YCBCRSUBSAMPLING, | |
343 | horizSubSampling, vertSubSampling); | |
344 | TIFFSetField(out, TIFFTAG_YCBCRPOSITIONING, YCBCRPOSITION_CENTERED); | |
345 | TIFFSetField(out, TIFFTAG_YCBCRCOEFFICIENTS, ycbcrCoeffs); | |
346 | rowsperstrip = TIFFDefaultStripSize(out, rowsperstrip); | |
347 | TIFFSetField(out, TIFFTAG_ROWSPERSTRIP, rowsperstrip); | |
348 | ||
80ed523f VZ |
349 | result = cvtRaster(out, raster, width, height); |
350 | _TIFFfree(raster); | |
351 | return result; | |
8414a40c VZ |
352 | } |
353 | ||
354 | char* stuff[] = { | |
355 | "usage: rgb2ycbcr [-c comp] [-r rows] [-h N] [-v N] input... output\n", | |
356 | "where comp is one of the following compression algorithms:\n", | |
357 | " jpeg\t\tJPEG encoding\n", | |
358 | " lzw\t\tLempel-Ziv & Welch encoding\n", | |
359 | " zip\t\tdeflate encoding\n", | |
360 | " packbits\tPackBits encoding (default)\n", | |
361 | " none\t\tno compression\n", | |
362 | "and the other options are:\n", | |
363 | " -r\trows/strip\n", | |
364 | " -h\thorizontal sampling factor (1,2,4)\n", | |
365 | " -v\tvertical sampling factor (1,2,4)\n", | |
366 | NULL | |
367 | }; | |
368 | ||
369 | static void | |
370 | usage(int code) | |
371 | { | |
372 | char buf[BUFSIZ]; | |
373 | int i; | |
374 | ||
375 | setbuf(stderr, buf); | |
376 | ||
377 | fprintf(stderr, "%s\n\n", TIFFGetVersion()); | |
378 | for (i = 0; stuff[i] != NULL; i++) | |
379 | fprintf(stderr, "%s\n", stuff[i]); | |
380 | exit(code); | |
381 | } | |
382 | ||
383 | /* vim: set ts=8 sts=8 sw=8 noet: */ | |
80ed523f VZ |
384 | /* |
385 | * Local Variables: | |
386 | * mode: c | |
387 | * c-basic-offset: 8 | |
388 | * fill-column: 78 | |
389 | * End: | |
390 | */ |