1
2
3 #ifdef _MSC_VER
4 #pragma warning(disable : 4996)
5 #endif
6 //#include "texception.h"
7 //#include "tfilepath.h"
8 //#include "tiio_jpg.h"
9 //#include "../compatibility/tnz4.h"
10
11 #include "tiio_jpg.h"
12 #include "tiio_jpg_exif.h"
13 #include "tproperty.h"
14 #include "tpixel.h"
15
16 /*
17 * Include file for users of JPEG library.
18 * You will need to have included system headers that define at least
19 * the typedefs FILE and size_t before you can include jpeglib.h.
20 * (stdio.h is sufficient on ANSI-conforming systems.)
21 * You may also wish to include "jerror.h".
22 */
23
24 #include <assert.h>
25 #include <stdio.h>
26
27 //=========================================================
28
29 const std::string Tiio::JpgWriterProperties::QUALITY("Quality");
30
31 //=========================================================
32
33 extern "C" {
tnz_error_exit(j_common_ptr cinfo)34 static void tnz_error_exit(j_common_ptr cinfo) {
35 // throw "merda";
36 }
37 }
38
39 #ifdef CICCIO
40 JMETHOD(void, error_exit, (j_common_ptr cinfo));
41 /* Conditionally emit a trace or warning message */
42 JMETHOD(void, emit_message, (j_common_ptr cinfo, int msg_level));
43 /* Routine that actually outputs a trace or error message */
44 JMETHOD(void, output_message, (j_common_ptr cinfo));
45 /* Format a message string for the most recent JPEG error or message */
46 JMETHOD(void, format_message, (j_common_ptr cinfo, char *buffer));
47 #define JMSG_LENGTH_MAX 200 /* recommended size of format_message buffer */
48 /* Reset error state variables at start of a new image */
49 JMETHOD(void, reset_error_mgr, (j_common_ptr cinfo));
50 #endif
51
52 using namespace Tiio;
53
JpgReader()54 JpgReader::JpgReader() : m_chan(0), m_isOpen(false) {
55 memset(&m_cinfo, 0, sizeof m_cinfo);
56 memset(&m_jerr, 0, sizeof m_jerr);
57 memset(&m_buffer, 0, sizeof m_buffer);
58 }
59
~JpgReader()60 JpgReader::~JpgReader() {
61 if (m_isOpen) {
62 try {
63 jpeg_finish_decompress(&m_cinfo);
64 jpeg_destroy_decompress(&m_cinfo);
65 } catch (...) {
66 }
67 }
68 if (m_chan) {
69 m_chan = 0;
70 }
71 }
72
getRowOrder() const73 Tiio::RowOrder JpgReader::getRowOrder() const { return Tiio::TOP2BOTTOM; }
74
open(FILE * file)75 void JpgReader::open(FILE *file) {
76 m_cinfo.err = jpeg_std_error(&m_jerr);
77 m_cinfo.err->error_exit = tnz_error_exit;
78
79 jpeg_create_decompress(&m_cinfo);
80
81 m_chan = file;
82 jpeg_stdio_src(&m_cinfo, m_chan);
83 jpeg_save_markers(&m_cinfo, JPEG_APP0 + 1, 0xffff); // EXIF
84 bool ret = jpeg_read_header(&m_cinfo, TRUE);
85
86 bool resolutionFoundInExif = false;
87 jpeg_saved_marker_ptr mark;
88 for (mark = m_cinfo.marker_list; NULL != mark; mark = mark->next) {
89 switch (mark->marker) {
90 case JPEG_APP0 + 1: // EXIF
91 JpgExifReader exifReader;
92 exifReader.process_EXIF(mark->data - 2, mark->data_length);
93 if (exifReader.containsResolution()) {
94 int resUnit = exifReader.getResolutionUnit();
95 resolutionFoundInExif = true;
96 if (resUnit == 1 || resUnit == 2) { // no unit(1) or inch(2)
97 m_info.m_dpix = (double)exifReader.getXResolution();
98 m_info.m_dpiy = (double)exifReader.getYResolution();
99 } else if (resUnit == 3) { // centimeter(3);
100 m_info.m_dpix = (double)exifReader.getXResolution() * 2.54;
101 m_info.m_dpiy = (double)exifReader.getYResolution() * 2.54;
102 } else // ignore millimeter(4) and micrometer(5) cases for now
103 resolutionFoundInExif = false;
104 }
105 break;
106 }
107 }
108
109 ret = ret && jpeg_start_decompress(&m_cinfo);
110 if (!ret) return;
111
112 int row_stride = m_cinfo.output_width * m_cinfo.output_components;
113 m_buffer = (*m_cinfo.mem->alloc_sarray)((j_common_ptr)&m_cinfo, JPOOL_IMAGE,
114 row_stride, 1);
115
116 m_info.m_lx = m_cinfo.output_width;
117 m_info.m_ly = m_cinfo.output_height;
118 m_info.m_samplePerPixel = 3;
119 m_info.m_valid = true;
120 m_isOpen = true;
121
122 if (!resolutionFoundInExif && (m_cinfo.saw_JFIF_marker != 0) &&
123 (m_cinfo.X_density != 1) && (m_cinfo.Y_density != 1)) {
124 if (m_cinfo.density_unit == 1) {
125 m_info.m_dpix = (double)m_cinfo.X_density;
126 m_info.m_dpiy = (double)m_cinfo.Y_density;
127 } else if (m_cinfo.density_unit == 2) {
128 m_info.m_dpix = (double)m_cinfo.X_density * 2.54;
129 m_info.m_dpiy = (double)m_cinfo.Y_density * 2.54;
130 }
131 }
132 }
133
readLine(char * buffer,int x0,int x1,int shrink)134 void JpgReader::readLine(char *buffer, int x0, int x1, int shrink) {
135 if (m_cinfo.out_color_space == JCS_RGB && m_cinfo.out_color_components == 3) {
136 int ret = jpeg_read_scanlines(&m_cinfo, m_buffer, 1);
137 assert(ret == 1);
138 unsigned char *src = m_buffer[0];
139 TPixel32 *dst = (TPixel32 *)buffer;
140 dst += x0;
141 src += 3 * x0;
142
143 int width = (m_cinfo.output_width - 1) / shrink + 1;
144 if (x1 >= x0) width = (x1 - x0) / shrink + 1;
145
146 while (--width >= 0) {
147 dst->r = src[0];
148 dst->g = src[1];
149 dst->b = src[2];
150 dst->m = (char)255;
151 src += 3 * shrink;
152 dst += shrink;
153 }
154 } else if (m_cinfo.out_color_components == 1) {
155 int ret = jpeg_read_scanlines(&m_cinfo, m_buffer, 1);
156 assert(ret == 1);
157 unsigned char *src = m_buffer[0];
158 TPixel32 *dst = (TPixel32 *)buffer;
159
160 dst += x0;
161 src += x0;
162
163 int width = (m_cinfo.output_width - 1) / shrink + 1;
164 if (x1 >= x0) width = (x1 - x0) / shrink + 1;
165
166 while (--width >= 0) {
167 dst->r = *src;
168 dst->g = *src;
169 dst->b = *src;
170 dst->m = (char)255;
171 src += shrink;
172 dst += shrink;
173 }
174 }
175 }
176
skipLines(int lineCount)177 int JpgReader::skipLines(int lineCount) {
178 for (int i = 0; i < lineCount; i++) {
179 int ret = jpeg_read_scanlines(&m_cinfo, m_buffer, 1);
180 assert(ret == 1);
181 }
182 return lineCount;
183 }
184
185 class JpgWriter final : public Tiio::Writer {
186 struct jpeg_compress_struct m_cinfo;
187 struct jpeg_error_mgr m_jerr;
188 FILE *m_chan;
189 JSAMPARRAY m_buffer;
190 bool m_headerWritten;
191
192 public:
JpgWriter()193 JpgWriter() : m_chan(0), m_headerWritten(false) {}
194
open(FILE * file,const TImageInfo & info)195 void open(FILE *file, const TImageInfo &info) override {
196 m_cinfo.err = jpeg_std_error(&m_jerr);
197 jpeg_create_compress(&m_cinfo);
198
199 m_cinfo.image_width = info.m_lx;
200 m_cinfo.image_height = info.m_ly;
201 m_cinfo.input_components = 3;
202 m_cinfo.in_color_space = JCS_RGB;
203
204 jpeg_set_defaults(&m_cinfo);
205
206 // save dpi always in JFIF header, instead of EXIF
207 m_cinfo.write_JFIF_header = 1;
208 m_cinfo.JFIF_major_version = 1;
209 m_cinfo.JFIF_minor_version = 2;
210 m_cinfo.X_density = (UINT16)info.m_dpix;
211 m_cinfo.Y_density = (UINT16)info.m_dpiy;
212 m_cinfo.density_unit = 1; // dot per inch
213 m_cinfo.write_Adobe_marker = 0;
214
215 if (!m_properties) m_properties = new Tiio::JpgWriterProperties();
216
217 int quality =
218 ((TIntProperty *)(m_properties->getProperty("Quality")))->getValue();
219
220 jpeg_set_quality(&m_cinfo, quality, TRUE);
221 m_cinfo.smoothing_factor =
222 ((TIntProperty *)(m_properties->getProperty("Smoothing")))->getValue();
223
224 // set horizontal and vertical chroma subsampling factor to encoder
225 // according to the quality value.
226 if (quality >= 70) { // none chroma-subsampling (4:4:4)
227 m_cinfo.comp_info[0].h_samp_factor = 1;
228 m_cinfo.comp_info[0].v_samp_factor = 1;
229 } else if (quality >= 30) { // medium chroma-subsampling (4:2:2)
230 m_cinfo.comp_info[0].h_samp_factor = 2;
231 m_cinfo.comp_info[0].v_samp_factor = 1;
232 } else { // quality < 30, high chroma-subsampling (4:1:1)
233 m_cinfo.comp_info[0].h_samp_factor = 2;
234 m_cinfo.comp_info[0].v_samp_factor = 2;
235 }
236 m_cinfo.comp_info[1].h_samp_factor = 1;
237 m_cinfo.comp_info[1].v_samp_factor = 1;
238 m_cinfo.comp_info[2].h_samp_factor = 1;
239 m_cinfo.comp_info[2].v_samp_factor = 1;
240
241 int row_stride = m_cinfo.image_width * m_cinfo.input_components;
242 m_buffer = (*m_cinfo.mem->alloc_sarray)((j_common_ptr)&m_cinfo, JPOOL_IMAGE,
243 row_stride, 1);
244
245 m_chan = file;
246 jpeg_stdio_dest(&m_cinfo, m_chan);
247 }
248
~JpgWriter()249 ~JpgWriter() {
250 jpeg_finish_compress(&m_cinfo);
251 jpeg_destroy_compress(&m_cinfo);
252 delete m_properties;
253 }
254
flush()255 void flush() override { fflush(m_chan); }
256
getRowOrder() const257 Tiio::RowOrder getRowOrder() const override { return Tiio::TOP2BOTTOM; }
258
writeLine(char * buffer)259 void writeLine(char *buffer) override {
260 if (!m_headerWritten) {
261 m_headerWritten = true;
262 jpeg_start_compress(&m_cinfo, TRUE);
263 }
264 TPixel32 *src = (TPixel32 *)buffer;
265 unsigned char *dst = m_buffer[0];
266 int lx = m_cinfo.image_width;
267 while (--lx >= 0) {
268 dst[0] = src->r;
269 dst[1] = src->g;
270 dst[2] = src->b;
271 dst += 3;
272 ++src;
273 }
274 jpeg_write_scanlines(&m_cinfo, m_buffer, 1);
275 }
276
277 // jpeg format does not support alpha channel
writeAlphaSupported() const278 bool writeAlphaSupported() const override { return false; }
279 };
280
281 //----
282
updateTranslation()283 void Tiio::JpgWriterProperties::updateTranslation() {
284 m_quality.setQStringName(tr("Quality"));
285 m_smoothing.setQStringName(tr("Smoothing"));
286 }
287
288 //----
289 //----
290
makeJpgReader()291 Tiio::Reader *Tiio::makeJpgReader() { return new JpgReader(); }
292
makeJpgWriter()293 Tiio::Writer *Tiio::makeJpgWriter() { return new JpgWriter(); }
294