1 | //======================================================================== |
2 | // |
3 | // JpegWriter.cc |
4 | // |
5 | // This file is licensed under the GPLv2 or later |
6 | // |
7 | // Copyright (C) 2009 Stefan Thomas <thomas@eload24.com> |
8 | // Copyright (C) 2010, 2012, 2017 Adrian Johnson <ajohnson@redneon.com> |
9 | // Copyright (C) 2010 Harry Roberts <harry.roberts@midnight-labs.org> |
10 | // Copyright (C) 2011 Thomas Freitag <Thomas.Freitag@alfa.de> |
11 | // Copyright (C) 2013 Peter Breitenlohner <peb@mppmu.mpg.de> |
12 | // Copyright (C) 2017, 2018, 2022 Albert Astals Cid <aacid@kde.org> |
13 | // Copyright (C) 2018 Martin Packman <gzlist@googlemail.com> |
14 | // Copyright (C) 2018 Ed Porras <ed@motologic.com> |
15 | // Copyright (C) 2021 Peter Williams <peter@newton.cx> |
16 | // Copyright (C) 2023 Jordan Abrahams-Whitehead <ajordanr@google.com> |
17 | // |
18 | //======================================================================== |
19 | |
20 | #include "JpegWriter.h" |
21 | |
22 | #include <limits> |
23 | |
24 | #ifdef ENABLE_LIBJPEG |
25 | |
26 | # include "poppler/Error.h" |
27 | # include <cstdio> |
28 | extern "C" { |
29 | # include <jpeglib.h> |
30 | } |
31 | |
32 | struct JpegWriterPrivate |
33 | { |
34 | bool progressive; |
35 | bool optimize; |
36 | int quality; |
37 | JpegWriter::Format format; |
38 | struct jpeg_compress_struct cinfo; |
39 | struct jpeg_error_mgr jerr; |
40 | }; |
41 | |
42 | static void outputMessage(j_common_ptr cinfo) |
43 | { |
44 | char buffer[JMSG_LENGTH_MAX]; |
45 | |
46 | // Create the message |
47 | (*cinfo->err->format_message)(cinfo, buffer); |
48 | |
49 | // Send it to poppler's error handler |
50 | error(category: errInternal, pos: -1, msg: "{0:s}" , buffer); |
51 | } |
52 | |
53 | JpegWriter::JpegWriter(int q, bool p, Format formatA) |
54 | { |
55 | priv = new JpegWriterPrivate; |
56 | priv->progressive = p; |
57 | priv->optimize = false; |
58 | priv->quality = q; |
59 | priv->format = formatA; |
60 | } |
61 | |
62 | JpegWriter::JpegWriter(Format formatA) : JpegWriter(-1, false, formatA) { } |
63 | |
64 | JpegWriter::~JpegWriter() |
65 | { |
66 | // cleanup |
67 | jpeg_destroy_compress(cinfo: &priv->cinfo); |
68 | delete priv; |
69 | } |
70 | |
71 | void JpegWriter::setQuality(int quality) |
72 | { |
73 | priv->quality = quality; |
74 | } |
75 | |
76 | void JpegWriter::setProgressive(bool progressive) |
77 | { |
78 | priv->progressive = progressive; |
79 | } |
80 | |
81 | void JpegWriter::setOptimize(bool optimize) |
82 | { |
83 | priv->optimize = optimize; |
84 | } |
85 | |
86 | bool JpegWriter::init(FILE *f, int width, int height, double hDPI, double vDPI) |
87 | { |
88 | if (hDPI < 0 || vDPI < 0 || hDPI > std::numeric_limits<UINT16>::max() || vDPI > std::numeric_limits<UINT16>::max()) { |
89 | error(category: errInternal, pos: -1, msg: "JpegWriter::init: hDPI or vDPI values are invalid {0:f} {1:f}" , hDPI, vDPI); |
90 | return false; |
91 | } |
92 | |
93 | // Setup error handler |
94 | priv->cinfo.err = jpeg_std_error(err: &priv->jerr); |
95 | priv->jerr.output_message = &outputMessage; |
96 | |
97 | // Initialize libjpeg |
98 | jpeg_create_compress(&priv->cinfo); |
99 | |
100 | // First set colorspace and call jpeg_set_defaults() since |
101 | // jpeg_set_defaults() sets default values for all fields in |
102 | // cinfo based on the colorspace. |
103 | switch (priv->format) { |
104 | case RGB: |
105 | priv->cinfo.in_color_space = JCS_RGB; |
106 | break; |
107 | case GRAY: |
108 | priv->cinfo.in_color_space = JCS_GRAYSCALE; |
109 | break; |
110 | case CMYK: |
111 | priv->cinfo.in_color_space = JCS_CMYK; |
112 | break; |
113 | default: |
114 | return false; |
115 | } |
116 | jpeg_set_defaults(cinfo: &priv->cinfo); |
117 | |
118 | // Set destination file |
119 | jpeg_stdio_dest(cinfo: &priv->cinfo, outfile: f); |
120 | |
121 | // Set libjpeg configuration |
122 | priv->cinfo.image_width = width; |
123 | priv->cinfo.image_height = height; |
124 | priv->cinfo.density_unit = 1; // dots per inch |
125 | priv->cinfo.X_density = static_cast<UINT16>(hDPI); |
126 | priv->cinfo.Y_density = static_cast<UINT16>(vDPI); |
127 | switch (priv->format) { |
128 | case GRAY: |
129 | priv->cinfo.input_components = 1; |
130 | break; |
131 | case RGB: |
132 | priv->cinfo.input_components = 3; |
133 | break; |
134 | case CMYK: |
135 | priv->cinfo.input_components = 4; |
136 | jpeg_set_colorspace(cinfo: &priv->cinfo, colorspace: JCS_YCCK); |
137 | priv->cinfo.write_JFIF_header = TRUE; |
138 | break; |
139 | default: |
140 | return false; |
141 | } |
142 | |
143 | // Set quality |
144 | if (priv->quality >= 0 && priv->quality <= 100) { |
145 | jpeg_set_quality(cinfo: &priv->cinfo, quality: priv->quality, TRUE); |
146 | } |
147 | |
148 | // Use progressive mode |
149 | if (priv->progressive) { |
150 | jpeg_simple_progression(cinfo: &priv->cinfo); |
151 | } |
152 | |
153 | // Set whether to compute optimal Huffman coding tables |
154 | priv->cinfo.optimize_coding = static_cast<boolean>(priv->optimize); |
155 | |
156 | // Get ready for data |
157 | jpeg_start_compress(cinfo: &priv->cinfo, TRUE); |
158 | |
159 | return true; |
160 | } |
161 | |
162 | bool JpegWriter::writePointers(unsigned char **rowPointers, int rowCount) |
163 | { |
164 | if (priv->format == CMYK) { |
165 | for (int y = 0; y < rowCount; y++) { |
166 | unsigned char *row = rowPointers[y]; |
167 | for (unsigned int x = 0; x < priv->cinfo.image_width; x++) { |
168 | for (int n = 0; n < 4; n++) { |
169 | *row = 0xff - *row; |
170 | row++; |
171 | } |
172 | } |
173 | } |
174 | } |
175 | // Write all rows to the file |
176 | jpeg_write_scanlines(cinfo: &priv->cinfo, scanlines: rowPointers, num_lines: rowCount); |
177 | |
178 | return true; |
179 | } |
180 | |
181 | bool JpegWriter::writeRow(unsigned char **rowPointer) |
182 | { |
183 | if (priv->format == CMYK) { |
184 | unsigned char *row = rowPointer[0]; |
185 | for (unsigned int x = 0; x < priv->cinfo.image_width; x++) { |
186 | for (int n = 0; n < 4; n++) { |
187 | *row = 0xff - *row; |
188 | row++; |
189 | } |
190 | } |
191 | } |
192 | // Write the row to the file |
193 | jpeg_write_scanlines(cinfo: &priv->cinfo, scanlines: rowPointer, num_lines: 1); |
194 | |
195 | return true; |
196 | } |
197 | |
198 | bool JpegWriter::close() |
199 | { |
200 | jpeg_finish_compress(cinfo: &priv->cinfo); |
201 | |
202 | return true; |
203 | } |
204 | |
205 | bool JpegWriter::supportCMYK() |
206 | { |
207 | return priv->format == CMYK; |
208 | } |
209 | |
210 | #endif |
211 | |