|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
|
2 * This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "nsCRT.h" |
|
7 #include "nsPNGEncoder.h" |
|
8 #include "prprf.h" |
|
9 #include "nsString.h" |
|
10 #include "nsStreamUtils.h" |
|
11 |
|
12 using namespace mozilla; |
|
13 |
|
14 NS_IMPL_ISUPPORTS(nsPNGEncoder, imgIEncoder, nsIInputStream, nsIAsyncInputStream) |
|
15 |
|
16 nsPNGEncoder::nsPNGEncoder() : mPNG(nullptr), mPNGinfo(nullptr), |
|
17 mIsAnimation(false), |
|
18 mFinished(false), |
|
19 mImageBuffer(nullptr), mImageBufferSize(0), |
|
20 mImageBufferUsed(0), mImageBufferReadPoint(0), |
|
21 mCallback(nullptr), |
|
22 mCallbackTarget(nullptr), mNotifyThreshold(0), |
|
23 mReentrantMonitor("nsPNGEncoder.mReentrantMonitor") |
|
24 { |
|
25 } |
|
26 |
|
27 nsPNGEncoder::~nsPNGEncoder() |
|
28 { |
|
29 if (mImageBuffer) { |
|
30 moz_free(mImageBuffer); |
|
31 mImageBuffer = nullptr; |
|
32 } |
|
33 // don't leak if EndImageEncode wasn't called |
|
34 if (mPNG) |
|
35 png_destroy_write_struct(&mPNG, &mPNGinfo); |
|
36 } |
|
37 |
|
38 // nsPNGEncoder::InitFromData |
|
39 // |
|
40 // One output option is supported: "transparency=none" means that the |
|
41 // output PNG will not have an alpha channel, even if the input does. |
|
42 // |
|
43 // Based partially on gfx/cairo/cairo/src/cairo-png.c |
|
44 // See also modules/libimg/png/libpng.txt |
|
45 |
|
46 NS_IMETHODIMP nsPNGEncoder::InitFromData(const uint8_t* aData, |
|
47 uint32_t aLength, // (unused, |
|
48 // req'd by JS) |
|
49 uint32_t aWidth, |
|
50 uint32_t aHeight, |
|
51 uint32_t aStride, |
|
52 uint32_t aInputFormat, |
|
53 const nsAString& aOutputOptions) |
|
54 { |
|
55 NS_ENSURE_ARG(aData); |
|
56 nsresult rv; |
|
57 |
|
58 rv = StartImageEncode(aWidth, aHeight, aInputFormat, aOutputOptions); |
|
59 if (!NS_SUCCEEDED(rv)) |
|
60 return rv; |
|
61 |
|
62 rv = AddImageFrame(aData, aLength, aWidth, aHeight, aStride, |
|
63 aInputFormat, aOutputOptions); |
|
64 if (!NS_SUCCEEDED(rv)) |
|
65 return rv; |
|
66 |
|
67 rv = EndImageEncode(); |
|
68 |
|
69 return rv; |
|
70 } |
|
71 |
|
72 |
|
73 // nsPNGEncoder::StartImageEncode |
|
74 // |
|
75 // |
|
76 // See ::InitFromData for other info. |
|
77 NS_IMETHODIMP nsPNGEncoder::StartImageEncode(uint32_t aWidth, |
|
78 uint32_t aHeight, |
|
79 uint32_t aInputFormat, |
|
80 const nsAString& aOutputOptions) |
|
81 { |
|
82 bool useTransparency = true, skipFirstFrame = false; |
|
83 uint32_t numFrames = 1; |
|
84 uint32_t numPlays = 0; // For animations, 0 == forever |
|
85 |
|
86 // can't initialize more than once |
|
87 if (mImageBuffer != nullptr) |
|
88 return NS_ERROR_ALREADY_INITIALIZED; |
|
89 |
|
90 // validate input format |
|
91 if (aInputFormat != INPUT_FORMAT_RGB && |
|
92 aInputFormat != INPUT_FORMAT_RGBA && |
|
93 aInputFormat != INPUT_FORMAT_HOSTARGB) |
|
94 return NS_ERROR_INVALID_ARG; |
|
95 |
|
96 // parse and check any provided output options |
|
97 nsresult rv = ParseOptions(aOutputOptions, &useTransparency, &skipFirstFrame, |
|
98 &numFrames, &numPlays, nullptr, nullptr, |
|
99 nullptr, nullptr, nullptr); |
|
100 if (rv != NS_OK) |
|
101 return rv; |
|
102 |
|
103 #ifdef PNG_APNG_SUPPORTED |
|
104 if (numFrames > 1) |
|
105 mIsAnimation = true; |
|
106 |
|
107 #endif |
|
108 |
|
109 // initialize |
|
110 mPNG = png_create_write_struct(PNG_LIBPNG_VER_STRING, |
|
111 nullptr, |
|
112 ErrorCallback, |
|
113 WarningCallback); |
|
114 if (! mPNG) |
|
115 return NS_ERROR_OUT_OF_MEMORY; |
|
116 |
|
117 mPNGinfo = png_create_info_struct(mPNG); |
|
118 if (! mPNGinfo) { |
|
119 png_destroy_write_struct(&mPNG, nullptr); |
|
120 return NS_ERROR_FAILURE; |
|
121 } |
|
122 |
|
123 // libpng's error handler jumps back here upon an error. |
|
124 // Note: It's important that all png_* callers do this, or errors |
|
125 // will result in a corrupt time-warped stack. |
|
126 if (setjmp(png_jmpbuf(mPNG))) { |
|
127 png_destroy_write_struct(&mPNG, &mPNGinfo); |
|
128 return NS_ERROR_FAILURE; |
|
129 } |
|
130 |
|
131 // Set up to read the data into our image buffer, start out with an 8K |
|
132 // estimated size. Note: we don't have to worry about freeing this data |
|
133 // in this function. It will be freed on object destruction. |
|
134 mImageBufferSize = 8192; |
|
135 mImageBuffer = (uint8_t*)moz_malloc(mImageBufferSize); |
|
136 if (!mImageBuffer) { |
|
137 png_destroy_write_struct(&mPNG, &mPNGinfo); |
|
138 return NS_ERROR_OUT_OF_MEMORY; |
|
139 } |
|
140 mImageBufferUsed = 0; |
|
141 |
|
142 // set our callback for libpng to give us the data |
|
143 png_set_write_fn(mPNG, this, WriteCallback, nullptr); |
|
144 |
|
145 // include alpha? |
|
146 int colorType; |
|
147 if ((aInputFormat == INPUT_FORMAT_HOSTARGB || |
|
148 aInputFormat == INPUT_FORMAT_RGBA) && |
|
149 useTransparency) |
|
150 colorType = PNG_COLOR_TYPE_RGB_ALPHA; |
|
151 else |
|
152 colorType = PNG_COLOR_TYPE_RGB; |
|
153 |
|
154 png_set_IHDR(mPNG, mPNGinfo, aWidth, aHeight, 8, colorType, |
|
155 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, |
|
156 PNG_FILTER_TYPE_DEFAULT); |
|
157 |
|
158 #ifdef PNG_APNG_SUPPORTED |
|
159 if (mIsAnimation) { |
|
160 png_set_first_frame_is_hidden(mPNG, mPNGinfo, skipFirstFrame); |
|
161 png_set_acTL(mPNG, mPNGinfo, numFrames, numPlays); |
|
162 } |
|
163 #endif |
|
164 |
|
165 // XXX: support PLTE, gAMA, tRNS, bKGD? |
|
166 |
|
167 png_write_info(mPNG, mPNGinfo); |
|
168 |
|
169 return NS_OK; |
|
170 } |
|
171 |
|
172 // Returns the number of bytes in the image buffer used. |
|
173 NS_IMETHODIMP nsPNGEncoder::GetImageBufferUsed(uint32_t *aOutputSize) |
|
174 { |
|
175 NS_ENSURE_ARG_POINTER(aOutputSize); |
|
176 *aOutputSize = mImageBufferUsed; |
|
177 return NS_OK; |
|
178 } |
|
179 |
|
180 // Returns a pointer to the start of the image buffer |
|
181 NS_IMETHODIMP nsPNGEncoder::GetImageBuffer(char **aOutputBuffer) |
|
182 { |
|
183 NS_ENSURE_ARG_POINTER(aOutputBuffer); |
|
184 *aOutputBuffer = reinterpret_cast<char*>(mImageBuffer); |
|
185 return NS_OK; |
|
186 } |
|
187 |
|
188 NS_IMETHODIMP nsPNGEncoder::AddImageFrame(const uint8_t* aData, |
|
189 uint32_t aLength, // (unused, |
|
190 // req'd by JS) |
|
191 uint32_t aWidth, |
|
192 uint32_t aHeight, |
|
193 uint32_t aStride, |
|
194 uint32_t aInputFormat, |
|
195 const nsAString& aFrameOptions) |
|
196 { |
|
197 bool useTransparency= true; |
|
198 uint32_t delay_ms = 500; |
|
199 #ifdef PNG_APNG_SUPPORTED |
|
200 uint32_t dispose_op = PNG_DISPOSE_OP_NONE; |
|
201 uint32_t blend_op = PNG_BLEND_OP_SOURCE; |
|
202 #else |
|
203 uint32_t dispose_op; |
|
204 uint32_t blend_op; |
|
205 #endif |
|
206 uint32_t x_offset = 0, y_offset = 0; |
|
207 |
|
208 // must be initialized |
|
209 if (mImageBuffer == nullptr) |
|
210 return NS_ERROR_NOT_INITIALIZED; |
|
211 |
|
212 // EndImageEncode was done, or some error occurred earlier |
|
213 if (!mPNG) |
|
214 return NS_BASE_STREAM_CLOSED; |
|
215 |
|
216 // validate input format |
|
217 if (aInputFormat != INPUT_FORMAT_RGB && |
|
218 aInputFormat != INPUT_FORMAT_RGBA && |
|
219 aInputFormat != INPUT_FORMAT_HOSTARGB) |
|
220 return NS_ERROR_INVALID_ARG; |
|
221 |
|
222 // libpng's error handler jumps back here upon an error. |
|
223 if (setjmp(png_jmpbuf(mPNG))) { |
|
224 png_destroy_write_struct(&mPNG, &mPNGinfo); |
|
225 return NS_ERROR_FAILURE; |
|
226 } |
|
227 |
|
228 // parse and check any provided output options |
|
229 nsresult rv = ParseOptions(aFrameOptions, &useTransparency, nullptr, |
|
230 nullptr, nullptr, &dispose_op, &blend_op, |
|
231 &delay_ms, &x_offset, &y_offset); |
|
232 if (rv != NS_OK) |
|
233 return rv; |
|
234 |
|
235 #ifdef PNG_APNG_SUPPORTED |
|
236 if (mIsAnimation) { |
|
237 // XXX the row pointers arg (#3) is unused, can it be removed? |
|
238 png_write_frame_head(mPNG, mPNGinfo, nullptr, |
|
239 aWidth, aHeight, x_offset, y_offset, |
|
240 delay_ms, 1000, dispose_op, blend_op); |
|
241 } |
|
242 #endif |
|
243 |
|
244 // Stride is the padded width of each row, so it better be longer |
|
245 // (I'm afraid people will not understand what stride means, so |
|
246 // check it well) |
|
247 if ((aInputFormat == INPUT_FORMAT_RGB && |
|
248 aStride < aWidth * 3) || |
|
249 ((aInputFormat == INPUT_FORMAT_RGBA || |
|
250 aInputFormat == INPUT_FORMAT_HOSTARGB) && |
|
251 aStride < aWidth * 4)) { |
|
252 NS_WARNING("Invalid stride for InitFromData/AddImageFrame"); |
|
253 return NS_ERROR_INVALID_ARG; |
|
254 } |
|
255 |
|
256 #ifdef PNG_WRITE_FILTER_SUPPORTED |
|
257 png_set_filter(mPNG, PNG_FILTER_TYPE_BASE, PNG_FILTER_VALUE_NONE); |
|
258 #endif |
|
259 |
|
260 // write each row: if we add more input formats, we may want to |
|
261 // generalize the conversions |
|
262 if (aInputFormat == INPUT_FORMAT_HOSTARGB) { |
|
263 // PNG requires RGBA with post-multiplied alpha, so we need to |
|
264 // convert |
|
265 uint8_t* row = new uint8_t[aWidth * 4]; |
|
266 for (uint32_t y = 0; y < aHeight; y ++) { |
|
267 ConvertHostARGBRow(&aData[y * aStride], row, aWidth, useTransparency); |
|
268 png_write_row(mPNG, row); |
|
269 } |
|
270 delete[] row; |
|
271 |
|
272 } else if (aInputFormat == INPUT_FORMAT_RGBA && ! useTransparency) { |
|
273 // RBGA, but we need to strip the alpha |
|
274 uint8_t* row = new uint8_t[aWidth * 4]; |
|
275 for (uint32_t y = 0; y < aHeight; y ++) { |
|
276 StripAlpha(&aData[y * aStride], row, aWidth); |
|
277 png_write_row(mPNG, row); |
|
278 } |
|
279 delete[] row; |
|
280 |
|
281 } else if (aInputFormat == INPUT_FORMAT_RGB || |
|
282 aInputFormat == INPUT_FORMAT_RGBA) { |
|
283 // simple RBG(A), no conversion needed |
|
284 for (uint32_t y = 0; y < aHeight; y ++) { |
|
285 png_write_row(mPNG, (uint8_t*)&aData[y * aStride]); |
|
286 } |
|
287 |
|
288 } else { |
|
289 NS_NOTREACHED("Bad format type"); |
|
290 return NS_ERROR_INVALID_ARG; |
|
291 } |
|
292 |
|
293 #ifdef PNG_APNG_SUPPORTED |
|
294 if (mIsAnimation) { |
|
295 png_write_frame_tail(mPNG, mPNGinfo); |
|
296 } |
|
297 #endif |
|
298 |
|
299 return NS_OK; |
|
300 } |
|
301 |
|
302 |
|
303 NS_IMETHODIMP nsPNGEncoder::EndImageEncode() |
|
304 { |
|
305 // must be initialized |
|
306 if (mImageBuffer == nullptr) |
|
307 return NS_ERROR_NOT_INITIALIZED; |
|
308 |
|
309 // EndImageEncode has already been called, or some error |
|
310 // occurred earlier |
|
311 if (!mPNG) |
|
312 return NS_BASE_STREAM_CLOSED; |
|
313 |
|
314 // libpng's error handler jumps back here upon an error. |
|
315 if (setjmp(png_jmpbuf(mPNG))) { |
|
316 png_destroy_write_struct(&mPNG, &mPNGinfo); |
|
317 return NS_ERROR_FAILURE; |
|
318 } |
|
319 |
|
320 png_write_end(mPNG, mPNGinfo); |
|
321 png_destroy_write_struct(&mPNG, &mPNGinfo); |
|
322 |
|
323 mFinished = true; |
|
324 NotifyListener(); |
|
325 |
|
326 // if output callback can't get enough memory, it will free our buffer |
|
327 if (!mImageBuffer) |
|
328 return NS_ERROR_OUT_OF_MEMORY; |
|
329 |
|
330 return NS_OK; |
|
331 } |
|
332 |
|
333 |
|
334 nsresult |
|
335 nsPNGEncoder::ParseOptions(const nsAString& aOptions, |
|
336 bool* useTransparency, |
|
337 bool* skipFirstFrame, |
|
338 uint32_t* numFrames, |
|
339 uint32_t* numPlays, |
|
340 uint32_t* frameDispose, |
|
341 uint32_t* frameBlend, |
|
342 uint32_t* frameDelay, |
|
343 uint32_t* offsetX, |
|
344 uint32_t* offsetY) |
|
345 { |
|
346 #ifdef PNG_APNG_SUPPORTED |
|
347 // Make a copy of aOptions, because strtok() will modify it. |
|
348 nsAutoCString optionsCopy; |
|
349 optionsCopy.Assign(NS_ConvertUTF16toUTF8(aOptions)); |
|
350 char* options = optionsCopy.BeginWriting(); |
|
351 |
|
352 while (char* token = nsCRT::strtok(options, ";", &options)) { |
|
353 // If there's an '=' character, split the token around it. |
|
354 char* equals = token, *value = nullptr; |
|
355 while(*equals != '=' && *equals) { |
|
356 ++equals; |
|
357 } |
|
358 if (*equals == '=') |
|
359 value = equals + 1; |
|
360 |
|
361 if (value) |
|
362 *equals = '\0'; // temporary null |
|
363 |
|
364 // transparency=[yes|no|none] |
|
365 if (nsCRT::strcmp(token, "transparency") == 0 && useTransparency) { |
|
366 if (!value) |
|
367 return NS_ERROR_INVALID_ARG; |
|
368 |
|
369 if (nsCRT::strcmp(value, "none") == 0 || |
|
370 nsCRT::strcmp(value, "no") == 0) { |
|
371 *useTransparency = false; |
|
372 } else if (nsCRT::strcmp(value, "yes") == 0) { |
|
373 *useTransparency = true; |
|
374 } else { |
|
375 return NS_ERROR_INVALID_ARG; |
|
376 } |
|
377 |
|
378 // skipfirstframe=[yes|no] |
|
379 } else if (nsCRT::strcmp(token, "skipfirstframe") == 0 && |
|
380 skipFirstFrame) { |
|
381 if (!value) |
|
382 return NS_ERROR_INVALID_ARG; |
|
383 |
|
384 if (nsCRT::strcmp(value, "no") == 0) { |
|
385 *skipFirstFrame = false; |
|
386 } else if (nsCRT::strcmp(value, "yes") == 0) { |
|
387 *skipFirstFrame = true; |
|
388 } else { |
|
389 return NS_ERROR_INVALID_ARG; |
|
390 } |
|
391 |
|
392 // frames=# |
|
393 } else if (nsCRT::strcmp(token, "frames") == 0 && numFrames) { |
|
394 if (!value) |
|
395 return NS_ERROR_INVALID_ARG; |
|
396 |
|
397 if (PR_sscanf(value, "%u", numFrames) != 1) { |
|
398 return NS_ERROR_INVALID_ARG; |
|
399 } |
|
400 |
|
401 // frames=0 is nonsense. |
|
402 if (*numFrames == 0) |
|
403 return NS_ERROR_INVALID_ARG; |
|
404 |
|
405 // plays=# |
|
406 } else if (nsCRT::strcmp(token, "plays") == 0 && numPlays) { |
|
407 if (!value) |
|
408 return NS_ERROR_INVALID_ARG; |
|
409 |
|
410 // plays=0 to loop forever, otherwise play sequence specified |
|
411 // number of times |
|
412 if (PR_sscanf(value, "%u", numPlays) != 1) |
|
413 return NS_ERROR_INVALID_ARG; |
|
414 |
|
415 // dispose=[none|background|previous] |
|
416 } else if (nsCRT::strcmp(token, "dispose") == 0 && frameDispose) { |
|
417 if (!value) |
|
418 return NS_ERROR_INVALID_ARG; |
|
419 |
|
420 if (nsCRT::strcmp(value, "none") == 0) { |
|
421 *frameDispose = PNG_DISPOSE_OP_NONE; |
|
422 } else if (nsCRT::strcmp(value, "background") == 0) { |
|
423 *frameDispose = PNG_DISPOSE_OP_BACKGROUND; |
|
424 } else if (nsCRT::strcmp(value, "previous") == 0) { |
|
425 *frameDispose = PNG_DISPOSE_OP_PREVIOUS; |
|
426 } else { |
|
427 return NS_ERROR_INVALID_ARG; |
|
428 } |
|
429 |
|
430 // blend=[source|over] |
|
431 } else if (nsCRT::strcmp(token, "blend") == 0 && frameBlend) { |
|
432 if (!value) |
|
433 return NS_ERROR_INVALID_ARG; |
|
434 |
|
435 if (nsCRT::strcmp(value, "source") == 0) { |
|
436 *frameBlend = PNG_BLEND_OP_SOURCE; |
|
437 } else if (nsCRT::strcmp(value, "over") == 0) { |
|
438 *frameBlend = PNG_BLEND_OP_OVER; |
|
439 } else { |
|
440 return NS_ERROR_INVALID_ARG; |
|
441 } |
|
442 |
|
443 // delay=# (in ms) |
|
444 } else if (nsCRT::strcmp(token, "delay") == 0 && frameDelay) { |
|
445 if (!value) |
|
446 return NS_ERROR_INVALID_ARG; |
|
447 |
|
448 if (PR_sscanf(value, "%u", frameDelay) != 1) |
|
449 return NS_ERROR_INVALID_ARG; |
|
450 |
|
451 // xoffset=# |
|
452 } else if (nsCRT::strcmp(token, "xoffset") == 0 && offsetX) { |
|
453 if (!value) |
|
454 return NS_ERROR_INVALID_ARG; |
|
455 |
|
456 if (PR_sscanf(value, "%u", offsetX) != 1) |
|
457 return NS_ERROR_INVALID_ARG; |
|
458 |
|
459 // yoffset=# |
|
460 } else if (nsCRT::strcmp(token, "yoffset") == 0 && offsetY) { |
|
461 if (!value) |
|
462 return NS_ERROR_INVALID_ARG; |
|
463 |
|
464 if (PR_sscanf(value, "%u", offsetY) != 1) |
|
465 return NS_ERROR_INVALID_ARG; |
|
466 |
|
467 // unknown token name |
|
468 } else |
|
469 return NS_ERROR_INVALID_ARG; |
|
470 |
|
471 if (value) |
|
472 *equals = '='; // restore '=' so strtok doesn't get lost |
|
473 } |
|
474 |
|
475 #endif |
|
476 return NS_OK; |
|
477 } |
|
478 |
|
479 |
|
480 /* void close (); */ |
|
481 NS_IMETHODIMP nsPNGEncoder::Close() |
|
482 { |
|
483 if (mImageBuffer != nullptr) { |
|
484 moz_free(mImageBuffer); |
|
485 mImageBuffer = nullptr; |
|
486 mImageBufferSize = 0; |
|
487 mImageBufferUsed = 0; |
|
488 mImageBufferReadPoint = 0; |
|
489 } |
|
490 return NS_OK; |
|
491 } |
|
492 |
|
493 /* unsigned long available (); */ |
|
494 NS_IMETHODIMP nsPNGEncoder::Available(uint64_t *_retval) |
|
495 { |
|
496 if (!mImageBuffer) |
|
497 return NS_BASE_STREAM_CLOSED; |
|
498 |
|
499 *_retval = mImageBufferUsed - mImageBufferReadPoint; |
|
500 return NS_OK; |
|
501 } |
|
502 |
|
503 /* [noscript] unsigned long read (in charPtr aBuf, |
|
504 in unsigned long aCount); */ |
|
505 NS_IMETHODIMP nsPNGEncoder::Read(char * aBuf, uint32_t aCount, |
|
506 uint32_t *_retval) |
|
507 { |
|
508 return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval); |
|
509 } |
|
510 |
|
511 /* [noscript] unsigned long readSegments (in nsWriteSegmentFun aWriter, |
|
512 in voidPtr aClosure, |
|
513 in unsigned long aCount); */ |
|
514 NS_IMETHODIMP nsPNGEncoder::ReadSegments(nsWriteSegmentFun aWriter, |
|
515 void *aClosure, uint32_t aCount, |
|
516 uint32_t *_retval) |
|
517 { |
|
518 // Avoid another thread reallocing the buffer underneath us |
|
519 ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor); |
|
520 |
|
521 uint32_t maxCount = mImageBufferUsed - mImageBufferReadPoint; |
|
522 if (maxCount == 0) { |
|
523 *_retval = 0; |
|
524 return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; |
|
525 } |
|
526 |
|
527 if (aCount > maxCount) |
|
528 aCount = maxCount; |
|
529 nsresult rv = |
|
530 aWriter(this, aClosure, |
|
531 reinterpret_cast<const char*>(mImageBuffer+mImageBufferReadPoint), |
|
532 0, aCount, _retval); |
|
533 if (NS_SUCCEEDED(rv)) { |
|
534 NS_ASSERTION(*_retval <= aCount, "bad write count"); |
|
535 mImageBufferReadPoint += *_retval; |
|
536 } |
|
537 |
|
538 // errors returned from the writer end here! |
|
539 return NS_OK; |
|
540 } |
|
541 |
|
542 /* boolean isNonBlocking (); */ |
|
543 NS_IMETHODIMP nsPNGEncoder::IsNonBlocking(bool *_retval) |
|
544 { |
|
545 *_retval = true; |
|
546 return NS_OK; |
|
547 } |
|
548 |
|
549 NS_IMETHODIMP nsPNGEncoder::AsyncWait(nsIInputStreamCallback *aCallback, |
|
550 uint32_t aFlags, |
|
551 uint32_t aRequestedCount, |
|
552 nsIEventTarget *aTarget) |
|
553 { |
|
554 if (aFlags != 0) |
|
555 return NS_ERROR_NOT_IMPLEMENTED; |
|
556 |
|
557 if (mCallback || mCallbackTarget) |
|
558 return NS_ERROR_UNEXPECTED; |
|
559 |
|
560 mCallbackTarget = aTarget; |
|
561 // 0 means "any number of bytes except 0" |
|
562 mNotifyThreshold = aRequestedCount; |
|
563 if (!aRequestedCount) |
|
564 mNotifyThreshold = 1024; // We don't want to notify incessantly |
|
565 |
|
566 // We set the callback absolutely last, because NotifyListener uses it to |
|
567 // determine if someone needs to be notified. If we don't set it last, |
|
568 // NotifyListener might try to fire off a notification to a null target |
|
569 // which will generally cause non-threadsafe objects to be used off the main thread |
|
570 mCallback = aCallback; |
|
571 |
|
572 // What we are being asked for may be present already |
|
573 NotifyListener(); |
|
574 return NS_OK; |
|
575 } |
|
576 |
|
577 NS_IMETHODIMP nsPNGEncoder::CloseWithStatus(nsresult aStatus) |
|
578 { |
|
579 return Close(); |
|
580 } |
|
581 |
|
582 // nsPNGEncoder::ConvertHostARGBRow |
|
583 // |
|
584 // Our colors are stored with premultiplied alphas, but PNGs use |
|
585 // post-multiplied alpha. This swaps to PNG-style alpha. |
|
586 // |
|
587 // Copied from gfx/cairo/cairo/src/cairo-png.c |
|
588 |
|
589 void |
|
590 nsPNGEncoder::ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest, |
|
591 uint32_t aPixelWidth, |
|
592 bool aUseTransparency) |
|
593 { |
|
594 uint32_t pixelStride = aUseTransparency ? 4 : 3; |
|
595 for (uint32_t x = 0; x < aPixelWidth; x ++) { |
|
596 const uint32_t& pixelIn = ((const uint32_t*)(aSrc))[x]; |
|
597 uint8_t *pixelOut = &aDest[x * pixelStride]; |
|
598 |
|
599 uint8_t alpha = (pixelIn & 0xff000000) >> 24; |
|
600 if (alpha == 0) { |
|
601 pixelOut[0] = pixelOut[1] = pixelOut[2] = pixelOut[3] = 0; |
|
602 } else { |
|
603 pixelOut[0] = (((pixelIn & 0xff0000) >> 16) * 255 + alpha / 2) / alpha; |
|
604 pixelOut[1] = (((pixelIn & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha; |
|
605 pixelOut[2] = (((pixelIn & 0x0000ff) >> 0) * 255 + alpha / 2) / alpha; |
|
606 if (aUseTransparency) |
|
607 pixelOut[3] = alpha; |
|
608 } |
|
609 } |
|
610 } |
|
611 |
|
612 |
|
613 // nsPNGEncoder::StripAlpha |
|
614 // |
|
615 // Input is RGBA, output is RGB |
|
616 |
|
617 void |
|
618 nsPNGEncoder::StripAlpha(const uint8_t* aSrc, uint8_t* aDest, |
|
619 uint32_t aPixelWidth) |
|
620 { |
|
621 for (uint32_t x = 0; x < aPixelWidth; x ++) { |
|
622 const uint8_t* pixelIn = &aSrc[x * 4]; |
|
623 uint8_t* pixelOut = &aDest[x * 3]; |
|
624 pixelOut[0] = pixelIn[0]; |
|
625 pixelOut[1] = pixelIn[1]; |
|
626 pixelOut[2] = pixelIn[2]; |
|
627 } |
|
628 } |
|
629 |
|
630 |
|
631 // nsPNGEncoder::WarningCallback |
|
632 |
|
633 void // static |
|
634 nsPNGEncoder::WarningCallback(png_structp png_ptr, |
|
635 png_const_charp warning_msg) |
|
636 { |
|
637 #ifdef DEBUG |
|
638 // XXX: these messages are probably useful callers... |
|
639 // use nsIConsoleService? |
|
640 PR_fprintf(PR_STDERR, "PNG Encoder: %s\n", warning_msg);; |
|
641 #endif |
|
642 } |
|
643 |
|
644 |
|
645 // nsPNGEncoder::ErrorCallback |
|
646 |
|
647 void // static |
|
648 nsPNGEncoder::ErrorCallback(png_structp png_ptr, |
|
649 png_const_charp error_msg) |
|
650 { |
|
651 #ifdef DEBUG |
|
652 // XXX: these messages are probably useful callers... |
|
653 // use nsIConsoleService? |
|
654 PR_fprintf(PR_STDERR, "PNG Encoder: %s\n", error_msg);; |
|
655 #endif |
|
656 #if PNG_LIBPNG_VER < 10500 |
|
657 longjmp(png_ptr->jmpbuf, 1); |
|
658 #else |
|
659 png_longjmp(png_ptr, 1); |
|
660 #endif |
|
661 } |
|
662 |
|
663 |
|
664 // nsPNGEncoder::WriteCallback |
|
665 |
|
666 void // static |
|
667 nsPNGEncoder::WriteCallback(png_structp png, png_bytep data, |
|
668 png_size_t size) |
|
669 { |
|
670 nsPNGEncoder* that = static_cast<nsPNGEncoder*>(png_get_io_ptr(png)); |
|
671 if (! that->mImageBuffer) |
|
672 return; |
|
673 |
|
674 if (that->mImageBufferUsed + size > that->mImageBufferSize) { |
|
675 // When we're reallocing the buffer we need to take the lock to ensure |
|
676 // that nobody is trying to read from the buffer we are destroying |
|
677 ReentrantMonitorAutoEnter autoEnter(that->mReentrantMonitor); |
|
678 |
|
679 // expand buffer, just double each time |
|
680 that->mImageBufferSize *= 2; |
|
681 uint8_t* newBuf = (uint8_t*)moz_realloc(that->mImageBuffer, |
|
682 that->mImageBufferSize); |
|
683 if (! newBuf) { |
|
684 // can't resize, just zero (this will keep us from writing more) |
|
685 moz_free(that->mImageBuffer); |
|
686 that->mImageBuffer = nullptr; |
|
687 that->mImageBufferSize = 0; |
|
688 that->mImageBufferUsed = 0; |
|
689 return; |
|
690 } |
|
691 that->mImageBuffer = newBuf; |
|
692 } |
|
693 memcpy(&that->mImageBuffer[that->mImageBufferUsed], data, size); |
|
694 that->mImageBufferUsed += size; |
|
695 that->NotifyListener(); |
|
696 } |
|
697 |
|
698 void |
|
699 nsPNGEncoder::NotifyListener() |
|
700 { |
|
701 // We might call this function on multiple threads (any threads that call |
|
702 // AsyncWait and any that do encoding) so we lock to avoid notifying the |
|
703 // listener twice about the same data (which generally leads to a truncated |
|
704 // image). |
|
705 ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor); |
|
706 |
|
707 if (mCallback && |
|
708 (mImageBufferUsed - mImageBufferReadPoint >= mNotifyThreshold || |
|
709 mFinished)) { |
|
710 nsCOMPtr<nsIInputStreamCallback> callback; |
|
711 if (mCallbackTarget) { |
|
712 callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget); |
|
713 } else { |
|
714 callback = mCallback; |
|
715 } |
|
716 |
|
717 NS_ASSERTION(callback, "Shouldn't fail to make the callback"); |
|
718 // Null the callback first because OnInputStreamReady could reenter |
|
719 // AsyncWait |
|
720 mCallback = nullptr; |
|
721 mCallbackTarget = nullptr; |
|
722 mNotifyThreshold = 0; |
|
723 |
|
724 callback->OnInputStreamReady(this); |
|
725 } |
|
726 } |