1
2//
3// This source file is part of appleseed.
4// Visit http://appleseedhq.net/ for additional information and resources.
5//
6// This software is released under the MIT license.
7//
8// Copyright (c) 2010-2013 Francois Beaune, Jupiter Jazz Limited
9// Copyright (c) 2014-2017 Francois Beaune, The appleseedhq Organization
10//
11// Permission is hereby granted, free of charge, to any person obtaining a copy
12// of this software and associated documentation files (the "Software"), to deal
13// in the Software without restriction, including without limitation the rights
14// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15// copies of the Software, and to permit persons to whom the Software is
16// furnished to do so, subject to the following conditions:
17//
18// The above copyright notice and this permission notice shall be included in
19// all copies or substantial portions of the Software.
20//
21// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27// THE SOFTWARE.
28//
29
30// Interface header.
31#include "pngimagefilewriter.h"
32
33// appleseed.foundation headers.
34#include "foundation/core/exceptions/exceptionioerror.h"
35#include "foundation/image/canvasproperties.h"
36#include "foundation/image/color.h"
37#include "foundation/image/icanvas.h"
38#include "foundation/image/tile.h"
39#include "foundation/platform/types.h"
40#include "foundation/utility/foreach.h"
41#include "foundation/utility/string.h"
42
43// libpng headers.
44#include "png.h"
45
46// Standard headers.
47#include <cassert>
48#include <cstddef>
49#include <cstdio>
50#include <string>
51#include <vector>
52
53using namespace std;
54
55namespace foundation
56{
57
58//
59// PNGImageFileWriter class implementation.
60//
61
62namespace
63{
64 // Fatal error callback.
65 void error_callback(png_structp png_ptr, png_const_charp error_msg)
66 {
67 // Close the file.
68 FILE* fp = static_cast<FILE*>(png_get_error_ptr(png_ptr));
69 fclose(fp);
70
71 // Deallocate the png_struct structure.
72 png_destroy_write_struct(&png_ptr, 0);
73
74 // Throw an exception.
75 throw Exception(error_msg);
76 }
77
78 // Warning callback.
79 void warning_callback(png_structp png_ptr, png_const_charp warning_msg)
80 {
81 // todo: implement.
82 }
83
84 // Add a text chunk for a given (key, text) pair.
85 void add_text_chunk(
86 vector<png_text_struct>& text_chunks,
87 const string& key,
88 const string& text)
89 {
90 png_text text_chunk;
91 text_chunk.key = duplicate_string(key.c_str());
92 text_chunk.text = duplicate_string(text.c_str());
93 text_chunk.compression = PNG_TEXT_COMPRESSION_NONE;
94 text_chunks.push_back(text_chunk);
95 }
96
97 // Add image attributes to a PNG file.
98 void add_attributes(
99 png_structp png_ptr,
100 png_infop info_ptr,
101 vector<png_text_struct>& text_chunks,
102 const ImageAttributes& image_attributes)
103 {
104 for (const_each<ImageAttributes> i = image_attributes; i; ++i)
105 {
106 // Fetch the name and the value of the attribute.
107 const string attr_name = i->key();
108 const string attr_value = i->value<string>();
109
110 if (attr_name == "title")
111 add_text_chunk(text_chunks, "Title", attr_value);
112
113 else if (attr_name == "author")
114 add_text_chunk(text_chunks, "Author", attr_value);
115
116 else if (attr_name == "description")
117 add_text_chunk(text_chunks, "Description", attr_value);
118
119 else if (attr_name == "copyright")
120 add_text_chunk(text_chunks, "Copyright", attr_value);
121
122 else if (attr_name == "creation_time")
123 add_text_chunk(text_chunks, "Creation Time", attr_value);
124
125 else if (attr_name == "software")
126 add_text_chunk(text_chunks, "Software", attr_value);
127
128 else if (attr_name == "disclaimer")
129 add_text_chunk(text_chunks, "Disclaimer", attr_value);
130
131 else if (attr_name == "warning")
132 add_text_chunk(text_chunks, "Warning", attr_value);
133
134 else if (attr_name == "source")
135 add_text_chunk(text_chunks, "Source", attr_value);
136
137 else if (attr_name == "comment")
138 add_text_chunk(text_chunks, "Comment", attr_value);
139
140 else if (attr_name == "dpi")
141 {
142 const size_t dpi = from_string<size_t>(attr_value);
143 const double dpm = dpi * (100.0 / 2.54);
144 png_set_pHYs(
145 png_ptr,
146 info_ptr,
147 static_cast<png_uint_32>(dpm),
148 static_cast<png_uint_32>(dpm),
149 PNG_RESOLUTION_METER);
150 }
151
152 else
153 add_text_chunk(text_chunks, attr_name, attr_value);
154 }
155 }
156
157 // Deallocate text chunks.
158 void destroy_text_chunks(vector<png_text_struct>& text_chunks)
159 {
160 for (size_t i = 0; i < text_chunks.size(); ++i)
161 {
162 free_string(text_chunks[i].key);
163 free_string(text_chunks[i].text);
164 }
165
166 text_chunks.clear();
167 }
168}
169
170void PNGImageFileWriter::write(
171 const char* filename,
172 const ICanvas& image,
173 const ImageAttributes& image_attributes)
174{
175 // Retrieve canvas properties.
176 const CanvasProperties& props = image.properties();
177
178 // todo: lift these limitations.
179 assert(props.m_channel_count == 3 || props.m_channel_count == 4);
180
181 // Open the file in write mode.
182 FILE* fp = fopen(filename, "wb");
183 if (fp == 0)
184 throw ExceptionIOError();
185
186 // Allocate and initialize the png_struct structure.
187 png_structp png_ptr =
188 png_create_write_struct(
189 PNG_LIBPNG_VER_STRING,
190 fp,
191 error_callback,
192 warning_callback);
193 if (png_ptr == 0)
194 {
195 fclose(fp);
196 throw ExceptionMemoryError();
197 }
198
199 // Allocate the png_info structure.
200 png_infop info_ptr = png_create_info_struct(png_ptr);
201 if (info_ptr == 0)
202 {
203 png_destroy_write_struct(&png_ptr, 0);
204 fclose(fp);
205 throw ExceptionMemoryError();
206 }
207
208 // Set up the output control.
209 png_init_io(png_ptr, fp);
210
211 // Set image information.
212 png_set_IHDR(
213 png_ptr,
214 info_ptr,
215 static_cast<png_uint_32>(props.m_canvas_width),
216 static_cast<png_uint_32>(props.m_canvas_height),
217 8, // bit depth -- todo: allow higher bit depths
218 props.m_channel_count == 4
219 ? PNG_COLOR_TYPE_RGB_ALPHA
220 : PNG_COLOR_TYPE_RGB,
221 PNG_INTERLACE_NONE,
222 PNG_COMPRESSION_TYPE_DEFAULT,
223 PNG_FILTER_TYPE_DEFAULT);
224
225 // Mark the image as being sRGB (implying specific gamma and color matching functions).
226 // See http://www.vias.org/pngguide/chapter10_07.html for details about intents.
227 png_set_sRGB_gAMA_and_cHRM(
228 png_ptr,
229 info_ptr,
230 PNG_sRGB_INTENT_PERCEPTUAL); // todo: allow the user to select different intents
231
232 // Set the number of significant bits for each of the R, G, B and A channels.
233 // todo: are we required to provide these information?
234 png_color_8 sig_bit;
235 sig_bit.red = 8;
236 sig_bit.green = 8;
237 sig_bit.blue = 8;
238 sig_bit.alpha = 8;
239 sig_bit.gray = 8; // for completeness
240 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
241
242 // Add image attributes.
243 vector<png_text_struct> text_chunks;
244 add_attributes(
245 png_ptr,
246 info_ptr,
247 text_chunks,
248 image_attributes);
249 if (!text_chunks.empty())
250 {
251 png_set_text(
252 png_ptr,
253 info_ptr,
254 &text_chunks[0],
255 static_cast<int>(text_chunks.size()));
256 }
257
258 // Write the file header information.
259 png_write_info(png_ptr, info_ptr);
260
261 // Create the temporary buffer holding one row of tiles in target format.
262 vector<uint8> buffer(
263 props.m_canvas_width
264 * props.m_tile_height
265 * props.m_channel_count);
266
267 // Construct pointers to each row of the temporary buffer.
268 vector<uint8*> buffer_rows(props.m_tile_height);
269 for (size_t y = 0; y < props.m_tile_height; ++y)
270 buffer_rows[y] = &buffer[y * props.m_canvas_width * props.m_channel_count];
271
272 // Loop over the rows of tiles.
273 for (size_t tile_y = 0; tile_y < props.m_tile_count_y; ++tile_y)
274 {
275 // Convert this row of tiles to target format.
276 for (size_t tile_x = 0; tile_x < props.m_tile_count_x; ++tile_x)
277 {
278 const Tile& tile = image.tile(tile_x, tile_y);
279 assert(tile.get_height() <= props.m_tile_height);
280 for (size_t y = 0; y < tile.get_height(); ++y)
281 {
282 for (size_t x = 0; x < tile.get_width(); ++x)
283 {
284 // Horizontal coordinate of the pixel in the temporary buffer.
285 const size_t buffer_x = tile_x * props.m_tile_width + x;
286
287 // Index of the pixel in the temporary buffer.
288 const size_t buffer_index =
289 (y * props.m_canvas_width + buffer_x) * props.m_channel_count;
290
291 // Fetch the pixel at coordinates (x, y) in the tile,
292 // perform format conversion if necessary, and store
293 // the converted pixel into the temporary buffer.
294 if (tile.get_channel_count() == 3)
295 {
296 Color3b pixel;
297 tile.get_pixel(x, y, pixel);
298 buffer[buffer_index + 0] = pixel[0];
299 buffer[buffer_index + 1] = pixel[1];
300 buffer[buffer_index + 2] = pixel[2];
301 }
302 else
303 {
304 assert(tile.get_channel_count() == 4);
305 Color4b pixel;
306 tile.get_pixel(x, y, pixel);
307 buffer[buffer_index + 0] = pixel[0];
308 buffer[buffer_index + 1] = pixel[1];
309 buffer[buffer_index + 2] = pixel[2];
310 buffer[buffer_index + 3] = pixel[3];
311 }
312 }
313 }
314 }
315
316 // Write this row of tiles to the file.
317 const size_t row_count = image.tile(0, tile_y).get_height();
318 png_write_rows(
319 png_ptr,
320 &buffer_rows[0],
321 static_cast<png_uint_32>(row_count));
322 }
323
324 // Finish writing the file.
325 png_write_end(png_ptr, 0);
326
327 // Deallocate the png_struct and png_info structures.
328 png_destroy_write_struct(&png_ptr, &info_ptr);
329
330 // Deallocate text chunks.
331 destroy_text_chunks(text_chunks);
332
333 // Close the file.
334 fclose(fp);
335}
336
337} // namespace foundation
338