1 | //======================================================================== |
2 | // |
3 | // TiffWriter.cc |
4 | // |
5 | // This file is licensed under the GPLv2 or later |
6 | // |
7 | // Copyright (C) 2010, 2012 William Bader <williambader@hotmail.com> |
8 | // Copyright (C) 2012, 2021, 2022 Albert Astals Cid <aacid@kde.org> |
9 | // Copyright (C) 2012, 2017 Adrian Johnson <ajohnson@redneon.com> |
10 | // Copyright (C) 2012 Pino Toscano <pino@kde.org> |
11 | // Copyright (C) 2014 Steven Lee <roc.sky@gmail.com> |
12 | // |
13 | //======================================================================== |
14 | |
15 | #include "TiffWriter.h" |
16 | |
17 | #ifdef ENABLE_LIBTIFF |
18 | |
19 | # include <cstring> |
20 | |
21 | # ifdef _WIN32 |
22 | # include <io.h> |
23 | # endif |
24 | |
25 | extern "C" { |
26 | # include <tiffio.h> |
27 | } |
28 | |
29 | # include <cstdint> |
30 | |
31 | struct TiffWriterPrivate |
32 | { |
33 | TIFF *f; // LibTiff file context |
34 | int numRows; // number of rows in the image |
35 | int curRow; // number of rows written |
36 | const char *compressionString; // compression type |
37 | TiffWriter::Format format; // format of image data |
38 | }; |
39 | |
40 | TiffWriter::~TiffWriter() |
41 | { |
42 | delete priv; |
43 | } |
44 | |
45 | TiffWriter::TiffWriter(Format formatA) |
46 | { |
47 | priv = new TiffWriterPrivate; |
48 | priv->f = nullptr; |
49 | priv->numRows = 0; |
50 | priv->curRow = 0; |
51 | priv->compressionString = nullptr; |
52 | priv->format = formatA; |
53 | } |
54 | |
55 | // Set the compression type |
56 | |
57 | void TiffWriter::setCompressionString(const char *compressionStringArg) |
58 | { |
59 | priv->compressionString = compressionStringArg; |
60 | } |
61 | |
62 | // Write a TIFF file. |
63 | |
64 | bool TiffWriter::init(FILE *openedFile, int width, int height, double hDPI, double vDPI) |
65 | { |
66 | unsigned int compression; |
67 | uint16_t photometric = 0; |
68 | uint32_t rowsperstrip = (uint32_t)-1; |
69 | int bitspersample; |
70 | uint16_t samplesperpixel = 0; |
71 | const struct compression_name_tag |
72 | { |
73 | const char *compressionName; // name of the compression option from the command line |
74 | unsigned int compressionCode; // internal libtiff code |
75 | const char *compressionDescription; // descriptive name |
76 | } compressionList[] = { { .compressionName: "none" , COMPRESSION_NONE, .compressionDescription: "no compression" }, |
77 | { .compressionName: "ccittrle" , COMPRESSION_CCITTRLE, .compressionDescription: "CCITT modified Huffman RLE" }, |
78 | { .compressionName: "ccittfax3" , COMPRESSION_CCITTFAX3, .compressionDescription: "CCITT Group 3 fax encoding" }, |
79 | { .compressionName: "ccittt4" , COMPRESSION_CCITT_T4, .compressionDescription: "CCITT T.4 (TIFF 6 name)" }, |
80 | { .compressionName: "ccittfax4" , COMPRESSION_CCITTFAX4, .compressionDescription: "CCITT Group 4 fax encoding" }, |
81 | { .compressionName: "ccittt6" , COMPRESSION_CCITT_T6, .compressionDescription: "CCITT T.6 (TIFF 6 name)" }, |
82 | { .compressionName: "lzw" , COMPRESSION_LZW, .compressionDescription: "Lempel-Ziv & Welch" }, |
83 | { .compressionName: "ojpeg" , COMPRESSION_OJPEG, .compressionDescription: "!6.0 JPEG" }, |
84 | { .compressionName: "jpeg" , COMPRESSION_JPEG, .compressionDescription: "%JPEG DCT compression" }, |
85 | { .compressionName: "next" , COMPRESSION_NEXT, .compressionDescription: "NeXT 2-bit RLE" }, |
86 | { .compressionName: "packbits" , COMPRESSION_PACKBITS, .compressionDescription: "Macintosh RLE" }, |
87 | { .compressionName: "ccittrlew" , COMPRESSION_CCITTRLEW, .compressionDescription: "CCITT modified Huffman RLE w/ word alignment" }, |
88 | { .compressionName: "deflate" , COMPRESSION_DEFLATE, .compressionDescription: "Deflate compression" }, |
89 | { .compressionName: "adeflate" , COMPRESSION_ADOBE_DEFLATE, .compressionDescription: "Deflate compression, as recognized by Adobe" }, |
90 | { .compressionName: "dcs" , COMPRESSION_DCS, .compressionDescription: "Kodak DCS encoding" }, |
91 | { .compressionName: "jbig" , COMPRESSION_JBIG, .compressionDescription: "ISO JBIG" }, |
92 | { .compressionName: "jp2000" , COMPRESSION_JP2000, .compressionDescription: "Leadtools JPEG2000" }, |
93 | { .compressionName: nullptr, .compressionCode: 0, .compressionDescription: nullptr } }; |
94 | |
95 | // Initialize |
96 | |
97 | priv->f = nullptr; |
98 | priv->curRow = 0; |
99 | |
100 | // Store the number of rows |
101 | |
102 | priv->numRows = height; |
103 | |
104 | // Set the compression |
105 | |
106 | compression = COMPRESSION_NONE; |
107 | |
108 | if (priv->compressionString == nullptr || strcmp(s1: priv->compressionString, s2: "" ) == 0) { |
109 | compression = COMPRESSION_NONE; |
110 | } else { |
111 | int i; |
112 | for (i = 0; compressionList[i].compressionName != nullptr; i++) { |
113 | if (strcmp(s1: priv->compressionString, s2: compressionList[i].compressionName) == 0) { |
114 | compression = compressionList[i].compressionCode; |
115 | break; |
116 | } |
117 | } |
118 | if (compressionList[i].compressionName == nullptr) { |
119 | fprintf(stderr, format: "TiffWriter: Unknown compression type '%.10s', using 'none'.\n" , priv->compressionString); |
120 | fprintf(stderr, format: "Known compression types (the tiff library might not support every type)\n" ); |
121 | for (i = 0; compressionList[i].compressionName != nullptr; i++) { |
122 | fprintf(stderr, format: "%10s %s\n" , compressionList[i].compressionName, compressionList[i].compressionDescription); |
123 | } |
124 | } |
125 | } |
126 | |
127 | // Set bits per sample, samples per pixel, and photometric type from format |
128 | |
129 | bitspersample = (priv->format == MONOCHROME ? 1 : 8); |
130 | |
131 | switch (priv->format) { |
132 | case MONOCHROME: |
133 | case GRAY: |
134 | samplesperpixel = 1; |
135 | photometric = PHOTOMETRIC_MINISBLACK; |
136 | break; |
137 | |
138 | case RGB: |
139 | samplesperpixel = 3; |
140 | photometric = PHOTOMETRIC_RGB; |
141 | break; |
142 | |
143 | case RGBA_PREMULTIPLIED: |
144 | samplesperpixel = 4; |
145 | photometric = PHOTOMETRIC_RGB; |
146 | break; |
147 | |
148 | case CMYK: |
149 | samplesperpixel = 4; |
150 | photometric = PHOTOMETRIC_SEPARATED; |
151 | break; |
152 | |
153 | case RGB48: |
154 | samplesperpixel = 3; |
155 | bitspersample = 16; |
156 | photometric = PHOTOMETRIC_RGB; |
157 | break; |
158 | } |
159 | |
160 | // Open the file |
161 | |
162 | if (openedFile == nullptr) { |
163 | fprintf(stderr, format: "TiffWriter: No output file given.\n" ); |
164 | return false; |
165 | } |
166 | |
167 | # ifdef _WIN32 |
168 | // Convert C Library handle to Win32 Handle |
169 | priv->f = TIFFFdOpen(_get_osfhandle(fileno(openedFile)), "-" , "w" ); |
170 | # else |
171 | priv->f = TIFFFdOpen(fileno(stream: openedFile), "-" , "w" ); |
172 | # endif |
173 | |
174 | if (!priv->f) { |
175 | return false; |
176 | } |
177 | |
178 | // Set TIFF tags |
179 | |
180 | TIFFSetField(priv->f, TIFFTAG_IMAGEWIDTH, width); |
181 | TIFFSetField(priv->f, TIFFTAG_IMAGELENGTH, height); |
182 | TIFFSetField(priv->f, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); |
183 | TIFFSetField(priv->f, TIFFTAG_SAMPLESPERPIXEL, samplesperpixel); |
184 | TIFFSetField(priv->f, TIFFTAG_BITSPERSAMPLE, bitspersample); |
185 | TIFFSetField(priv->f, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); |
186 | TIFFSetField(priv->f, TIFFTAG_PHOTOMETRIC, photometric); |
187 | TIFFSetField(priv->f, TIFFTAG_COMPRESSION, (uint16_t)compression); |
188 | TIFFSetField(priv->f, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif: priv->f, request: rowsperstrip)); |
189 | TIFFSetField(priv->f, TIFFTAG_XRESOLUTION, hDPI); |
190 | TIFFSetField(priv->f, TIFFTAG_YRESOLUTION, vDPI); |
191 | TIFFSetField(priv->f, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH); |
192 | |
193 | if (priv->format == RGBA_PREMULTIPLIED) { |
194 | uint16_t = EXTRASAMPLE_ASSOCALPHA; |
195 | TIFFSetField(priv->f, TIFFTAG_EXTRASAMPLES, 1, &extra); |
196 | } |
197 | |
198 | if (priv->format == CMYK) { |
199 | TIFFSetField(priv->f, TIFFTAG_INKSET, INKSET_CMYK); |
200 | TIFFSetField(priv->f, TIFFTAG_NUMBEROFINKS, 4); |
201 | } |
202 | |
203 | return true; |
204 | } |
205 | |
206 | bool TiffWriter::writePointers(unsigned char **rowPointers, int rowCount) |
207 | { |
208 | // Write all rows to the file |
209 | |
210 | for (int row = 0; row < rowCount; row++) { |
211 | if (TIFFWriteScanline(tif: priv->f, buf: rowPointers[row], row, sample: 0) < 0) { |
212 | fprintf(stderr, format: "TiffWriter: Error writing tiff row %d\n" , row); |
213 | return false; |
214 | } |
215 | } |
216 | |
217 | return true; |
218 | } |
219 | |
220 | bool TiffWriter::writeRow(unsigned char **rowData) |
221 | { |
222 | // Add a single row |
223 | |
224 | if (TIFFWriteScanline(tif: priv->f, buf: *rowData, row: priv->curRow, sample: 0) < 0) { |
225 | fprintf(stderr, format: "TiffWriter: Error writing tiff row %d\n" , priv->curRow); |
226 | return false; |
227 | } |
228 | |
229 | priv->curRow++; |
230 | |
231 | return true; |
232 | } |
233 | |
234 | bool TiffWriter::close() |
235 | { |
236 | // Close the file |
237 | |
238 | TIFFClose(tif: priv->f); |
239 | |
240 | return true; |
241 | } |
242 | |
243 | #endif |
244 | |