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