comparison OrthancFramework/Sources/Images/PngWriter.cpp @ 4642:69bbb4bd35cb

PngWriter recreates a new libpng context for each encoding
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 28 Apr 2021 11:22:39 +0200
parents d9473bd5ed43
children e8967149d87a
comparison
equal deleted inserted replaced
4641:b02dc8303cf6 4642:69bbb4bd35cb
60 //((PngWriter*) payload)->isError_ = true; 60 //((PngWriter*) payload)->isError_ = true;
61 } 61 }
62 62
63 static void WarningHandler(png_structp png, png_const_charp message) 63 static void WarningHandler(png_structp png, png_const_charp message)
64 { 64 {
65 printf("++ %d\n", (int)message); 65 printf("++ %d\n", (int)message);
66 }*/ 66 }*/
67 67
68 68
69 namespace Orthanc 69 namespace Orthanc
70 { 70 {
71 struct PngWriter::PImpl 71 /**
72 { 72 * The "png_" structure cannot be safely reused if the bpp changes
73 * between successive invocations. This can lead to "Invalid reads"
74 * reported by valgrind if writing a 16bpp image, then a 8bpp image
75 * using the same "PngWriter" object. Starting with Orthanc 1.9.3,
76 * we recreate a new "png_" context each time a PNG image must be
77 * written so as to prevent such invalid reads.
78 **/
79 class PngWriter::Context : public boost::noncopyable
80 {
81 private:
73 png_structp png_; 82 png_structp png_;
74 png_infop info_; 83 png_infop info_;
75 84
76 // Filled by Prepare() 85 // Filled by Prepare()
77 std::vector<uint8_t*> rows_; 86 std::vector<uint8_t*> rows_;
78 int bitDepth_; 87 int bitDepth_;
79 int colorType_; 88 int colorType_;
89
90 public:
91 Context() :
92 png_(NULL),
93 info_(NULL)
94 {
95 png_ = png_create_write_struct
96 (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); //this, ErrorHandler, WarningHandler);
97 if (!png_)
98 {
99 throw OrthancException(ErrorCode_NotEnoughMemory);
100 }
101
102 info_ = png_create_info_struct(png_);
103 if (!info_)
104 {
105 png_destroy_write_struct(&png_, NULL);
106 throw OrthancException(ErrorCode_NotEnoughMemory);
107 }
108 }
109
110
111 ~Context()
112 {
113 if (info_)
114 {
115 png_destroy_info_struct(png_, &info_);
116 }
117
118 if (png_)
119 {
120 png_destroy_write_struct(&png_, NULL);
121 }
122 }
123
124
125 png_structp GetObject() const
126 {
127 return png_;
128 }
129
130
131 void Prepare(unsigned int width,
132 unsigned int height,
133 unsigned int pitch,
134 PixelFormat format,
135 const void* buffer)
136 {
137 rows_.resize(height);
138 for (unsigned int y = 0; y < height; y++)
139 {
140 rows_[y] = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(buffer)) + y * pitch;
141 }
142
143 switch (format)
144 {
145 case PixelFormat_RGB24:
146 bitDepth_ = 8;
147 colorType_ = PNG_COLOR_TYPE_RGB;
148 break;
149
150 case PixelFormat_RGBA32:
151 bitDepth_ = 8;
152 colorType_ = PNG_COLOR_TYPE_RGBA;
153 break;
154
155 case PixelFormat_Grayscale8:
156 bitDepth_ = 8;
157 colorType_ = PNG_COLOR_TYPE_GRAY;
158 break;
159
160 case PixelFormat_Grayscale16:
161 case PixelFormat_SignedGrayscale16:
162 bitDepth_ = 16;
163 colorType_ = PNG_COLOR_TYPE_GRAY;
164 break;
165
166 default:
167 throw OrthancException(ErrorCode_NotImplemented);
168 }
169 }
170
171
172 void Compress(unsigned int width,
173 unsigned int height,
174 unsigned int pitch,
175 PixelFormat format)
176 {
177 png_set_IHDR(png_, info_, width, height,
178 bitDepth_, colorType_, PNG_INTERLACE_NONE,
179 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
180
181 png_write_info(png_, info_);
182
183 if (height > 0)
184 {
185 switch (format)
186 {
187 case PixelFormat_Grayscale16:
188 case PixelFormat_SignedGrayscale16:
189 {
190 int transforms = 0;
191 if (Toolbox::DetectEndianness() == Endianness_Little)
192 {
193 transforms = PNG_TRANSFORM_SWAP_ENDIAN;
194 }
195
196 png_set_rows(png_, info_, &rows_[0]);
197 png_write_png(png_, info_, transforms, NULL);
198
199 break;
200 }
201
202 default:
203 png_write_image(png_, &rows_[0]);
204 }
205 }
206
207 png_write_end(png_, NULL);
208 }
80 }; 209 };
81
82
83
84 PngWriter::PngWriter() : pimpl_(new PImpl)
85 {
86 pimpl_->png_ = NULL;
87 pimpl_->info_ = NULL;
88
89 pimpl_->png_ = png_create_write_struct
90 (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); //this, ErrorHandler, WarningHandler);
91 if (!pimpl_->png_)
92 {
93 throw OrthancException(ErrorCode_NotEnoughMemory);
94 }
95
96 pimpl_->info_ = png_create_info_struct(pimpl_->png_);
97 if (!pimpl_->info_)
98 {
99 png_destroy_write_struct(&pimpl_->png_, NULL);
100 throw OrthancException(ErrorCode_NotEnoughMemory);
101 }
102 }
103
104 PngWriter::~PngWriter()
105 {
106 if (pimpl_->info_)
107 {
108 png_destroy_info_struct(pimpl_->png_, &pimpl_->info_);
109 }
110
111 if (pimpl_->png_)
112 {
113 png_destroy_write_struct(&pimpl_->png_, NULL);
114 }
115 }
116
117
118
119 void PngWriter::Prepare(unsigned int width,
120 unsigned int height,
121 unsigned int pitch,
122 PixelFormat format,
123 const void* buffer)
124 {
125 pimpl_->rows_.resize(height);
126 for (unsigned int y = 0; y < height; y++)
127 {
128 pimpl_->rows_[y] = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(buffer)) + y * pitch;
129 }
130
131 switch (format)
132 {
133 case PixelFormat_RGB24:
134 pimpl_->bitDepth_ = 8;
135 pimpl_->colorType_ = PNG_COLOR_TYPE_RGB;
136 break;
137
138 case PixelFormat_RGBA32:
139 pimpl_->bitDepth_ = 8;
140 pimpl_->colorType_ = PNG_COLOR_TYPE_RGBA;
141 break;
142
143 case PixelFormat_Grayscale8:
144 pimpl_->bitDepth_ = 8;
145 pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY;
146 break;
147
148 case PixelFormat_Grayscale16:
149 case PixelFormat_SignedGrayscale16:
150 pimpl_->bitDepth_ = 16;
151 pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY;
152 break;
153
154 default:
155 throw OrthancException(ErrorCode_NotImplemented);
156 }
157 }
158
159
160 void PngWriter::Compress(unsigned int width,
161 unsigned int height,
162 unsigned int pitch,
163 PixelFormat format)
164 {
165 png_set_IHDR(pimpl_->png_, pimpl_->info_, width, height,
166 pimpl_->bitDepth_, pimpl_->colorType_, PNG_INTERLACE_NONE,
167 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
168
169 png_write_info(pimpl_->png_, pimpl_->info_);
170
171 if (height > 0)
172 {
173 switch (format)
174 {
175 case PixelFormat_Grayscale16:
176 case PixelFormat_SignedGrayscale16:
177 {
178 int transforms = 0;
179 if (Toolbox::DetectEndianness() == Endianness_Little)
180 {
181 transforms = PNG_TRANSFORM_SWAP_ENDIAN;
182 }
183
184 png_set_rows(pimpl_->png_, pimpl_->info_, &pimpl_->rows_[0]);
185 png_write_png(pimpl_->png_, pimpl_->info_, transforms, NULL);
186
187 break;
188 }
189
190 default:
191 png_write_image(pimpl_->png_, &pimpl_->rows_[0]);
192 }
193 }
194
195 png_write_end(pimpl_->png_, NULL);
196 }
197 210
198 211
199 #if ORTHANC_SANDBOXED == 0 212 #if ORTHANC_SANDBOXED == 0
200 void PngWriter::WriteToFileInternal(const std::string& filename, 213 void PngWriter::WriteToFileInternal(const std::string& filename,
201 unsigned int width, 214 unsigned int width,
202 unsigned int height, 215 unsigned int height,
203 unsigned int pitch, 216 unsigned int pitch,
204 PixelFormat format, 217 PixelFormat format,
205 const void* buffer) 218 const void* buffer)
206 { 219 {
207 Prepare(width, height, pitch, format, buffer); 220 Context context;
221
222 context.Prepare(width, height, pitch, format, buffer);
208 223
209 FILE* fp = SystemToolbox::OpenFile(filename, FileMode_WriteBinary); 224 FILE* fp = SystemToolbox::OpenFile(filename, FileMode_WriteBinary);
210 if (!fp) 225 if (!fp)
211 { 226 {
212 throw OrthancException(ErrorCode_CannotWriteFile); 227 throw OrthancException(ErrorCode_CannotWriteFile);
213 } 228 }
214 229
215 png_init_io(pimpl_->png_, fp); 230 png_init_io(context.GetObject(), fp);
216 231
217 if (setjmp(png_jmpbuf(pimpl_->png_))) 232 if (setjmp(png_jmpbuf(context.GetObject())))
218 { 233 {
219 // Error during writing PNG 234 // Error during writing PNG
220 throw OrthancException(ErrorCode_CannotWriteFile); 235 throw OrthancException(ErrorCode_CannotWriteFile);
221 } 236 }
222 237
223 Compress(width, height, pitch, format); 238 context.Compress(width, height, pitch, format);
224 239
225 fclose(fp); 240 fclose(fp);
226 } 241 }
227 #endif 242 #endif
228 243
232 png_size_t size) 247 png_size_t size)
233 { 248 {
234 ChunkedBuffer* buffer = reinterpret_cast<ChunkedBuffer*>(png_get_io_ptr(png_ptr)); 249 ChunkedBuffer* buffer = reinterpret_cast<ChunkedBuffer*>(png_get_io_ptr(png_ptr));
235 buffer->AddChunk(data, size); 250 buffer->AddChunk(data, size);
236 } 251 }
237
238 252
239 253
240 void PngWriter::WriteToMemoryInternal(std::string& png, 254 void PngWriter::WriteToMemoryInternal(std::string& png,
241 unsigned int width, 255 unsigned int width,
242 unsigned int height, 256 unsigned int height,
243 unsigned int pitch, 257 unsigned int pitch,
244 PixelFormat format, 258 PixelFormat format,
245 const void* buffer) 259 const void* buffer)
246 { 260 {
261 Context context;
262
247 ChunkedBuffer chunks; 263 ChunkedBuffer chunks;
248 264
249 Prepare(width, height, pitch, format, buffer); 265 context.Prepare(width, height, pitch, format, buffer);
250 266
251 if (setjmp(png_jmpbuf(pimpl_->png_))) 267 if (setjmp(png_jmpbuf(context.GetObject())))
252 { 268 {
253 // Error during writing PNG 269 // Error during writing PNG
254 throw OrthancException(ErrorCode_InternalError); 270 throw OrthancException(ErrorCode_InternalError);
255 } 271 }
256 272
257 png_set_write_fn(pimpl_->png_, &chunks, MemoryCallback, NULL); 273 png_set_write_fn(context.GetObject(), &chunks, MemoryCallback, NULL);
258 274
259 Compress(width, height, pitch, format); 275 context.Compress(width, height, pitch, format);
260 276
261 chunks.Flatten(png); 277 chunks.Flatten(png);
262 } 278 }
263 } 279 }